# import threading
import wx
from PIL import Image

from meerk40t.core.node.elem_path import PathNode
from meerk40t.core.units import UNITS_PER_INCH

# from meerk40t.gui.icons import icons8_image_50
# from meerk40t.gui.mwindow import MWindow
from meerk40t.gui.propertypanels.attributes import (
    IdPanel,
    PositionSizePanel,
    PreventChangePanel,
)
from meerk40t.gui.wxutils import ScrolledPanel, StaticBoxSizer, TextCtrl, dip_size
from meerk40t.svgelements import Matrix

_ = wx.GetTranslation

# The default value needs to be true, as the static method will be called before init happened...
HAS_VECTOR_ENGINE = True

class CropPanel(wx.Panel):
    name = _("Crop")
    priority = 5

    @staticmethod
    def accepts(node):
        if node.type != "elem image":
            return False
        for n in node.operations:
            if n.get("name") == "crop":
                return True
        return False

    def __init__(self, *args, context=None, node=None, **kwds):
        kwds["style"] = kwds.get("style", 0) | wx.TAB_TRAVERSAL
        wx.Panel.__init__(self, *args, **kwds)
        self.context = context
        self.node = node
        self._width = None
        self._height = None
        self._bounds = None
        self.op = None
        self._no_update = False

        self.check_enable_crop = wx.CheckBox(self, wx.ID_ANY, _("Enable"))
        self.button_reset = wx.Button(self, wx.ID_ANY, _("Reset"))

        self.label_info = wx.StaticText(self, wx.ID_ANY, "--")

        self.slider_left = wx.Slider(
            self, wx.ID_ANY, 0, -127, 127, style=wx.SL_AUTOTICKS | wx.SL_HORIZONTAL
        )
        self.slider_right = wx.Slider(
            self, wx.ID_ANY, 0, -127, 127, style=wx.SL_AUTOTICKS | wx.SL_HORIZONTAL
        )
        self.slider_top = wx.Slider(
            self, wx.ID_ANY, 0, -127, 127, style=wx.SL_AUTOTICKS | wx.SL_HORIZONTAL
        )
        self.slider_bottom = wx.Slider(
            self, wx.ID_ANY, 0, -127, 127, style=wx.SL_AUTOTICKS | wx.SL_HORIZONTAL
        )
        self.text_left = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY)
        self.text_right = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY)
        self.text_top = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY)
        self.text_bottom = wx.TextCtrl(self, wx.ID_ANY, style=wx.TE_READONLY)

        self.__set_properties()
        self.__do_layout()

        self.Bind(wx.EVT_BUTTON, self.on_button_reset, self.button_reset)
        self.Bind(wx.EVT_CHECKBOX, self.on_check_enable_crop, self.check_enable_crop)
        self.Bind(wx.EVT_SLIDER, self.on_slider_left, self.slider_left)
        self.Bind(wx.EVT_SLIDER, self.on_slider_right, self.slider_right)
        self.Bind(wx.EVT_SLIDER, self.on_slider_top, self.slider_top)
        self.Bind(wx.EVT_SLIDER, self.on_slider_bottom, self.slider_bottom)

        flag = False
        self.activate_controls(flag)
        self.set_widgets(node)

    def activate_controls(self, flag):
        self.button_reset.Enable(flag)
        self.slider_left.Enable(flag)
        self.slider_right.Enable(flag)
        self.slider_top.Enable(flag)
        self.slider_bottom.Enable(flag)

    def set_widgets(self, node):
        if self.node is None:
            self.label_info.SetLabel("")
            self.Hide()
            return
        else:
            self.Show()
        self._no_update = True
        self.node = node
        self.op = None
        self._width, self._height = self.node.image.size
        self._bounds = [0, 0, self._width, self._height]
        flag = False
        for n in node.operations:
            if n.get("name") == "crop":
                self.op = n
                break
        self._width, self._height = self.node.image.size
        self.label_info.SetLabel(f"{self._width} x {self._height} px")
        if self.op is not None:
            flag = self.op["enable"]
            self._bounds = self.op["bounds"]
            if self._bounds is None:
                self._bounds = [0, 0, self._width, self._height]
                self.op["bounds"] = self._bounds

        self.set_slider_limits("lrtb", False)

        self.check_enable_crop.SetValue(flag)
        self.activate_controls(flag)

        self.cropleft = self._bounds[0]
        self.cropright = self._bounds[2]
        self.croptop = self._bounds[1]
        self.cropbottom = self._bounds[3]
        self._no_update = False

    def __set_properties(self):
        self.slider_left.SetToolTip(
            _("How many pixels do you want to crop from the left?")
        )
        self.slider_right.SetToolTip(
            _("How many pixels do you want to crop from the right?")
        )
        self.slider_top.SetToolTip(
            _("How many pixels do you want to crop from the top?")
        )
        self.slider_bottom.SetToolTip(
            _("How many pixels do you want to crop from the bottom?")
        )

    def __do_layout(self):
        # begin wxGlade: ContrastPanel.__do_layout
        sizer_main = StaticBoxSizer(self, wx.ID_ANY, _("Image-Dimensions"), wx.VERTICAL)
        sizer_info = wx.BoxSizer(wx.HORIZONTAL)
        sizer_info.Add(self.check_enable_crop, 1, wx.ALIGN_CENTER_VERTICAL, 0)
        sizer_info.Add(self.button_reset, 0, wx.EXPAND, 0)
        sizer_info.Add(self.label_info, 0, wx.ALIGN_CENTER_VERTICAL, 0)

        sizer_left = wx.BoxSizer(wx.HORIZONTAL)
        sizer_right = wx.BoxSizer(wx.HORIZONTAL)
        sizer_top = wx.BoxSizer(wx.HORIZONTAL)
        sizer_bottom = wx.BoxSizer(wx.HORIZONTAL)

        lbl_left = wx.StaticText(self, wx.ID_ANY, _("Left"))
        lbl_left.SetMinSize(dip_size(self, 60, -1))
        lbl_right = wx.StaticText(self, wx.ID_ANY, _("Right"))
        lbl_right.SetMinSize(dip_size(self, 60, -1))
        lbl_bottom = wx.StaticText(self, wx.ID_ANY, _("Bottom"))
        lbl_bottom.SetMinSize(dip_size(self, 60, -1))
        lbl_top = wx.StaticText(self, wx.ID_ANY, _("Top"))
        lbl_top.SetMinSize(dip_size(self, 60, -1))

        self.text_left.SetMaxSize(dip_size(self, 60, -1))
        self.text_right.SetMaxSize(dip_size(self, 60, -1))
        self.text_top.SetMaxSize(dip_size(self, 60, -1))
        self.text_bottom.SetMaxSize(dip_size(self, 60, -1))

        sizer_left.Add(lbl_left, 0, wx.ALIGN_CENTER_VERTICAL)
        sizer_left.Add(self.slider_left, 4, wx.ALIGN_CENTER_VERTICAL)
        sizer_left.Add(self.text_left, 1, wx.ALIGN_CENTER_VERTICAL)

        sizer_right.Add(lbl_right, 0, wx.ALIGN_CENTER_VERTICAL)
        sizer_right.Add(self.slider_right, 4, wx.ALIGN_CENTER_VERTICAL)
        sizer_right.Add(self.text_right, 1, wx.ALIGN_CENTER_VERTICAL)

        sizer_top.Add(lbl_top, 0, wx.ALIGN_CENTER_VERTICAL)
        sizer_top.Add(self.slider_top, 4, wx.ALIGN_CENTER_VERTICAL)
        sizer_top.Add(self.text_top, 1, wx.ALIGN_CENTER_VERTICAL)

        sizer_bottom.Add(lbl_bottom, 0, wx.ALIGN_CENTER_VERTICAL)
        sizer_bottom.Add(self.slider_bottom, 4, wx.ALIGN_CENTER_VERTICAL)
        sizer_bottom.Add(self.text_bottom, 1, wx.ALIGN_CENTER_VERTICAL)

        sizer_main.Add(sizer_info, 0, wx.EXPAND, 0)
        sizer_main.Add(sizer_left, 0, wx.EXPAND, 0)
        sizer_main.Add(sizer_right, 0, wx.EXPAND, 0)
        sizer_main.Add(sizer_top, 0, wx.EXPAND, 0)
        sizer_main.Add(sizer_bottom, 0, wx.EXPAND, 0)

        self.SetSizer(sizer_main)
        sizer_main.Fit(self)
        self.Layout()

    def on_button_reset(self, event):
        if self.node is None:
            return
        w, h = self.node.image.size
        self._bounds = [0, 0, w, h]
        self.op["bounds"] = self._bounds
        self.set_slider_limits("lrtb")
        self.node.update(self.context)

    def on_check_enable_crop(self, event=None):
        flag = self.check_enable_crop.GetValue()
        if flag:
            if self.op is None:
                w, h = self.node.image.size
                self._width = w
                self._height = h
                self.op = {"name": "crop", "enable": True, "bounds": [0, 0, w, h]}
                self.node.operations.append(self.op)
                self.set_slider_limits("lrtb", False)
                last = self._no_update
                self._no_update = True
                self.cropleft = 0
                self.cropright = w
                self.croptop = 0
                self.cropbottom = h
                self._no_update = last
            else:
                self.op["enable"] = flag
        else:
            if self.op is not None:
                self.op["enable"] = flag
        if self.op is not None and not self._no_update:
            self.node.update(self.context)
        self.activate_controls(flag)

    def on_slider_left(self, event=None):
        self.cropleft = self.slider_left.GetValue()

    def on_slider_right(self, event=None):
        self.cropright = self.slider_right.GetValue()

    def on_slider_top(self, event=None):
        self.croptop = self.slider_top.GetValue()

    def on_slider_bottom(self, event=None):
        self.cropbottom = self.slider_bottom.GetValue()

    def set_slider_limits(self, pattern, constraint=True):
        if "l" in pattern:
            value = self._bounds[2]
            self.slider_left.SetMin(0)
            self.slider_left.SetMax(value - 1 if constraint else self._width)
            if self._bounds[0] != self.slider_left.GetValue():
                self.slider_left.SetValue(int(self._bounds[0]))
                dvalue = self._bounds[0]
                if dvalue == 0:
                    self.text_left.SetValue("---")
                else:
                    self.text_left.SetValue(f"> {dvalue} px")
        if "r" in pattern:
            value = self._bounds[0]
            self.slider_right.SetMin(value + 1 if constraint else 0)
            self.slider_right.SetMax(self._width)
            if self._bounds[2] != self.slider_right.GetValue():
                self.slider_right.SetValue(int(self._bounds[2]))
                dvalue = self._width - self._bounds[2]
                if dvalue == 0:
                    self.text_right.SetValue("---")
                else:
                    self.text_right.SetValue(f"{dvalue} px <")
        if "t" in pattern:
            value = self._bounds[3]
            self.slider_top.SetMin(0)
            self.slider_top.SetMax(value - 1 if constraint else self._height)
            if self._bounds[1] != self.slider_top.GetValue():
                self.slider_top.SetValue(int(self._bounds[1]))
                dvalue = self._bounds[1]
                if dvalue == 0:
                    self.text_top.SetValue("---")
                else:
                    self.text_top.SetValue(f"> {dvalue} px")
        if "b" in pattern:
            value = self._bounds[1]
            self.slider_bottom.SetMin(value + 1 if constraint else 0)
            self.slider_bottom.SetMax(self._height)
            if self._bounds[3] != self.slider_bottom.GetValue():
                self.slider_bottom.SetValue(int(self._bounds[3]))
                dvalue = self._height - self._bounds[3]
                if dvalue == 0:
                    self.text_bottom.SetValue("---")
                else:
                    self.text_bottom.SetValue(f"{dvalue} px <")

    @property
    def cropleft(self):
        if self._bounds is None:
            return None
        else:
            return self._bounds[0]

    @cropleft.setter
    def cropleft(self, value):
        # print(f"Set left to: {value}")
        self._bounds[0] = value
        if self.slider_left.GetValue() != value:
            self.slider_left.SetValue(int(value))
        if value == 0:
            self.text_left.SetValue("---")
        else:
            self.text_left.SetValue(f"> {value} px")
        # We need to adjust the boundaries of the right slider.
        self.set_slider_limits("r")
        if self.op is not None:
            self.op["bounds"][0] = value
            if not self._no_update:
                self.node.update(self.context)

    @property
    def cropright(self):
        if self._bounds is None:
            return None
        else:
            return self._bounds[2]

    @cropright.setter
    def cropright(self, value):
        # print(f"Set right to: {value}")
        self._bounds[2] = value
        if self.slider_right.GetValue() != value:
            self.slider_right.SetValue(int(value))
        dvalue = self._width - value
        if dvalue == 0:
            self.text_right.SetValue("---")
        else:
            self.text_right.SetValue(f"{dvalue} px <")
        # We need to adjust the boundaries of the left slider.
        self.set_slider_limits("l")
        if self.op is not None:
            self.op["bounds"][2] = value
            if not self._no_update:
                self.node.update(self.context)

    @property
    def croptop(self):
        if self._bounds is None:
            return None
        else:
            return self._bounds[1]

    @croptop.setter
    def croptop(self, value):
        # print(f"Set top to: {value}")
        self._bounds[1] = value
        if self.slider_top.GetValue() != value:
            self.slider_top.SetValue(int(value))
        if value == 0:
            self.text_top.SetValue("---")
        else:
            self.text_top.SetValue(f"> {value} px")
        # We need to adjust the boundaries of the bottom slider.
        self.set_slider_limits("b")
        if self.op is not None:
            self.op["bounds"][1] = value
            if not self._no_update:
                self.node.update(self.context)

    @property
    def cropbottom(self):
        if self._bounds is None:
            return None
        else:
            return self._bounds[3]

    @cropbottom.setter
    def cropbottom(self, value):
        self._bounds[3] = value
        if self.slider_bottom.GetValue() != value:
            self.slider_bottom.SetValue(int(value))
        # We need to adjust the boundaries of the top slider.
        self.set_slider_limits("t")
        if self.op is not None:
            self.op["bounds"][3] = value
            if not self._no_update:
                self.node.update(self.context)

class ImageModificationPanel(ScrolledPanel):
    name = _("Modification")
    priority = 90

    def __init__(self, *args, context=None, node=None, **kwargs):
        # begin wxGlade: ConsolePanel.__init__
        kwargs["style"] = kwargs.get("style", 0) | wx.TAB_TRAVERSAL
        wx.Panel.__init__(self, *args, **kwargs)
        self.context = context
        self.node = node
        self.scripts = []
        choices = [_("Set to None")]
        for entry in list(self.context.match("raster_script/.*", suffix=True)):
            self.scripts.append(entry)
            choices.append(_("Apply {entry}").format(entry=entry))
        self.combo_scripts = wx.ComboBox(
            self, wx.ID_ANY, choices=choices, style=wx.CB_READONLY | wx.CB_DROPDOWN
        )
        self.combo_scripts.SetSelection(0)
        self.button_apply = wx.Button(self, wx.ID_ANY, _("Apply Script"))
        self.list_operations = wx.ListCtrl(
            self,
            wx.ID_ANY,
            style=wx.LC_HRULES | wx.LC_REPORT | wx.LC_VRULES | wx.LC_SINGLE_SEL,
        )

        self._do_layout()
        self._do_logic()
        self.set_widgets(node)

    def _do_layout(self):
        self.list_operations.AppendColumn(_("#"), format=wx.LIST_FORMAT_LEFT, width=58)
        self.list_operations.AppendColumn(
            _("Action"), format=wx.LIST_FORMAT_LEFT, width=65
        )
        self.list_operations.AppendColumn(
            _("Active"), format=wx.LIST_FORMAT_LEFT, width=25
        )
        self.list_operations.AppendColumn(
            _("Parameters"), format=wx.LIST_FORMAT_LEFT, width=95
        )

        sizer_main = wx.BoxSizer(wx.VERTICAL)
        sizer_script = StaticBoxSizer(self, wx.ID_ANY, _("RasterWizard"), wx.HORIZONTAL)

        sizer_script.Add(self.combo_scripts, 1, wx.EXPAND, 0)
        sizer_script.Add(self.button_apply, 0, wx.EXPAND, 0)
        sizer_main.Add(sizer_script, 0, wx.EXPAND, 0)
        sizer_main.Add(self.list_operations, 1, wx.EXPAND, 0)

        self.SetSizer(sizer_main)
        self.Layout()
        self.Centre()

    def _do_logic(self):
        self.button_apply.Bind(wx.EVT_BUTTON, self.on_apply_replace)
        self.button_apply.Bind(wx.EVT_RIGHT_DOWN, self.on_apply_append)
        self.list_operations.Bind(wx.EVT_RIGHT_DOWN, self.on_list_menu)

    @staticmethod
    def accepts(node):
        if node.type == "elem image":
            return True
        return False

    def set_widgets(self, node=None):
        self.node = node
        if node is None:
            return
        self.fill_operations()

    def fill_operations(self):
        self.list_operations.DeleteAllItems()
        idx = 0
        for op in self.node.operations:
            idx += 1
            list_id = self.list_operations.InsertItem(
                self.list_operations.GetItemCount(), f"#{idx}"
            )
            self.list_operations.SetItem(list_id, 1, op["name"])
            self.list_operations.SetItem(list_id, 2, "x" if op["enable"] else "-")
            self.list_operations.SetItem(list_id, 3, str(op))

    def apply_script(self, index, addition):
        if index == 0:
            self.node.operations = []
            self.update_node()
        else:
            if index < 1 or index > len(self.scripts):
                return
            script = self.scripts[index - 1]
            raster_script = self.context.lookup(f"raster_script/{script}")
            if not addition:
                self.node.operations = []
            for entry in raster_script:
                self.node.operations.append(entry)
            self.update_node()

    def update_node(self):
        self.context.elements.emphasized()
        self.node.update(self.context)
        self.context.signal("element_property_update", self.node)
        self.context.signal("selected", self.node)

    def on_apply_replace(self, event):
        idx = self.combo_scripts.GetSelection()
        if idx >= 0:
            self.apply_script(idx, False)

    def on_apply_append(self, event):
        idx = self.combo_scripts.GetSelection()
        if idx >= 0:
            self.apply_script(idx, True)

    def on_list_menu(self, event):
        def on_delete(index):
            def check(event):
                self.node.operations.pop(index)
                self.update_node()

            return check

        def on_enable(index):
            def check(event):
                self.node.operations[index]["enable"] = not self.node.operations[index][
                    "enable"
                ]
                self.update_node()

            return check

        def on_op_insert(index, op):
            def check(event):
                self.node.operations.insert(index, op)
                self.update_node()

            return check

        def on_op_append(index, op):
            def check(event):
                self.node.operations.append(op)
                self.update_node()

            return check

        index = self.list_operations.GetFirstSelected()

        possible_ops = [
            {"name": "crop", "enable": True, "bounds": None},
            {
                "name": "grayscale",
                "enable": True,
                "invert": False,
                "red": 1.0,
                "green": 1.0,
                "blue": 1.0,
                "lightness": 1.0,
            },
            {"name": "auto_contrast", "enable": True, "cutoff": 3},
            {"name": "contrast", "enable": True, "contrast": 25, "brightness": 25},
            {
                "name": "unsharp_mask",
                "enable": True,
                "percent": 500,
                "radius": 4,
                "threshold": 0,
            },
            {
                "name": "tone",
                "type": "spline",
                "enable": True,
                "values": [[0, 0], [100, 150], [255, 255]],
            },
            {"name": "gamma", "enable": True, "factor": 3.5},
            {"name": "edge_enhance", "enable": False},
            {
                "name": "halftone",
                "enable": True,
                "black": True,
                "sample": 10,
                "angle": 22,
                "oversample": 2,
            },
            {"name": "dither", "enable": True, "type": "Floyd-Steinberg"},
        ]
        devmode = self.context.root.setting(bool, "developer_mode", False)
        menu = wx.Menu()
        if index >= 0:
            # Edit-Part
            menuitem = menu.Append(
                wx.ID_ANY, _("Delete item"), _("Will delete the current entry")
            )
            self.Bind(wx.EVT_MENU, on_delete(index), id=menuitem.GetId())

            menuitem = menu.Append(
                wx.ID_ANY,
                _("Enable"),
                _("Toggles enable-status of operation"),
                kind=wx.ITEM_CHECK,
            )
            menuitem.Check(self.node.operations[index]["enable"])
            self.Bind(wx.EVT_MENU, on_enable(index), id=menuitem.GetId())
            if devmode:
                menu.AppendSeparator()
                for op in possible_ops:
                    menuitem = menu.Append(
                        wx.ID_ANY,
                        _("Insert {op}").format(op=op["name"]),
                        _("Will insert this operation before the current entry"),
                    )
                    self.Bind(wx.EVT_MENU, on_op_insert(index, op), id=menuitem.GetId())
                menu.AppendSeparator()
        if devmode:
            for op in possible_ops:
                menuitem = menu.Append(
                    wx.ID_ANY,
                    _("Append {op}").format(op=op["name"]),
                    _("Will append this operation to the end of the list"),
                )
                self.Bind(wx.EVT_MENU, on_op_append(index, op), id=menuitem.GetId())

        if menu.MenuItemCount != 0:
            self.PopupMenu(menu)
            menu.Destroy()

    def pane_show(self):
        self.fill_operations()

    def pane_active(self):
        self.fill_operations()

class ImageVectorisationPanel(ScrolledPanel):
    name = _("Vectorisation")
    priority = 95

    def __init__(self, *args, context=None, node=None, **kwargs):
        # begin wxGlade: ConsolePanel.__init__
        kwargs["style"] = kwargs.get("style", 0) | wx.TAB_TRAVERSAL
        wx.Panel.__init__(self, *args, **kwargs)
        self.context = context
        self.node = node
        main_sizer = wx.BoxSizer(wx.HORIZONTAL)
        # self.vector_lock = threading.Lock()
        # self.alive = True
        # Only display if we have a vector engine
        make_vector = self.context.kernel.lookup("render-op/make_vector")
        if make_vector:
            global HAS_VECTOR_ENGINE
            HAS_VECTOR_ENGINE = True
        if not make_vector:
            main_sizer.Add(
                wx.StaticText(
                    self, wx.ID_ANY, "No vector engine installed, you need potrace"
                ),
                1,
                wx.EXPAND,
                0,
            )
            self.SetSizer(main_sizer)
            main_sizer.Fit(self)
            self.Layout()
            self.Centre()
            return

        sizer_options = StaticBoxSizer(self, wx.ID_ANY, _("Options"), wx.VERTICAL)
        main_sizer.Add(sizer_options, 1, wx.EXPAND, 0)

        sizer_turn = wx.BoxSizer(wx.HORIZONTAL)
        sizer_options.Add(sizer_turn, 0, wx.EXPAND, 0)

        label_turn = wx.StaticText(self, wx.ID_ANY, _("Turnpolicy"))
        label_turn.SetMinSize(dip_size(self, 70, -1))
        sizer_turn.Add(label_turn, 0, wx.ALIGN_CENTER_VERTICAL, 0)
        self.turn_choices = [
            "Black",
            "White",
            "Left",
            "Right",
            "Minority",
            "Majority",
            "Random",
        ]
        self.combo_turnpolicy = wx.ComboBox(
            self,
            wx.ID_ANY,
            choices=self.turn_choices,
            style=wx.CB_DROPDOWN | wx.CB_READONLY,
        )
        self.combo_turnpolicy.SetToolTip(
            _(
                "This parameter determines how to resolve ambiguities during decomposition of bitmaps into paths.\n\n"
                + "BLACK: prefers to connect black (foreground) components.\n"
                + "WHITE: prefers to connect white (background) components.\n"
                + "LEFT: always take a left turn.\n"
                + "RIGHT: always take a right turn.\n"
                + "MINORITY: prefers to connect the color (black or white) that occurs least frequently in a local neighborhood of the current position.\n"
                + "MAJORITY: prefers to connect the color (black or white) that occurs most frequently in a local neighborhood of the current position.\n"
                + "RANDOM: choose randomly."
            )
        )
        self.combo_turnpolicy.SetSelection(4)
        sizer_turn.Add(self.combo_turnpolicy, 1, wx.ALIGN_CENTER_VERTICAL, 0)

        sizer_turd = wx.BoxSizer(wx.HORIZONTAL)
        sizer_options.Add(sizer_turd, 0, wx.EXPAND, 0)

        label_turd = wx.StaticText(self, wx.ID_ANY, _("Despeckle"))
        label_turd.SetMinSize(dip_size(self, 70, -1))
        sizer_turd.Add(label_turd, 0, wx.ALIGN_CENTER_VERTICAL, 0)

        self.slider_turdsize = wx.Slider(self, wx.ID_ANY, 2, 0, 10)
        self.slider_turdsize.SetToolTip(
            _("Suppress speckles of up to this size (default 2 px)")
        )
        sizer_turd.Add(self.slider_turdsize, 1, wx.EXPAND, 0)

        sizer_alphamax = wx.BoxSizer(wx.HORIZONTAL)
        sizer_options.Add(sizer_alphamax, 0, wx.EXPAND, 0)

        label_alphamax = wx.StaticText(self, wx.ID_ANY, _("Corners"))
        label_alphamax.SetMinSize(dip_size(self, 70, -1))
        sizer_alphamax.Add(label_alphamax, 0, wx.ALIGN_CENTER_VERTICAL, 0)

        self.slider_alphamax = wx.Slider(self, wx.ID_ANY, 9, 0, 12)
        self.slider_alphamax.SetToolTip(
            _(
                "This parameter is a threshold for the detection of corners. It controls the smoothness of the traced curve."
            )
        )
        sizer_alphamax.Add(self.slider_alphamax, 1, wx.EXPAND, 0)

        sizer_opticurve = wx.BoxSizer(wx.HORIZONTAL)
        sizer_options.Add(sizer_opticurve, 0, wx.EXPAND, 0)

        label_opticurve = wx.StaticText(self, wx.ID_ANY, _("Simplify"))
        label_opticurve.SetMinSize(dip_size(self, 70, -1))
        sizer_opticurve.Add(label_opticurve, 0, wx.ALIGN_CENTER_VERTICAL, 0)

        self.check_opticurve = wx.CheckBox(self, wx.ID_ANY, "")
        self.check_opticurve.SetToolTip(
            _(
                "Try to 'simplify' the final curve by reducing the number of Bezier curve segments."
            )
        )
        self.check_opticurve.SetValue(1)
        sizer_opticurve.Add(self.check_opticurve, 0, wx.ALIGN_CENTER_VERTICAL, 0)

        sizer_opttolerance = wx.BoxSizer(wx.HORIZONTAL)
        sizer_options.Add(sizer_opttolerance, 0, wx.EXPAND, 0)

        label_opttolerance = wx.StaticText(self, wx.ID_ANY, _("Tolerance"))
        label_opttolerance.SetMinSize(dip_size(self, 70, -1))
        sizer_opttolerance.Add(label_opttolerance, 0, wx.ALIGN_CENTER_VERTICAL, 0)

        self.slider_tolerance = wx.Slider(self, wx.ID_ANY, 20, 0, 150)
        self.slider_tolerance.SetToolTip(
            _(
                "This defines the amount of error allowed in this simplification.\n"
                + "Larger values tend to decrease the number of segments, at the expense of less accuracy."
            )
        )
        sizer_opttolerance.Add(self.slider_tolerance, 1, wx.EXPAND, 0)

        sizer_blacklevel = wx.BoxSizer(wx.HORIZONTAL)
        sizer_options.Add(sizer_blacklevel, 0, wx.EXPAND, 0)

        label_blacklevel = wx.StaticText(self, wx.ID_ANY, _("Black-Level"))
        label_blacklevel.SetMinSize(dip_size(self, 70, -1))
        sizer_blacklevel.Add(label_blacklevel, 0, wx.ALIGN_CENTER_VERTICAL, 0)

        self.slider_blacklevel = wx.Slider(
            self, wx.ID_ANY, 50, 0, 100, style=wx.SL_HORIZONTAL | wx.SL_LABELS
        )
        self.slider_blacklevel.SetToolTip(_("Establish when 'black' starts"))
        sizer_blacklevel.Add(self.slider_blacklevel, 1, wx.EXPAND, 0)

        sizer_buttons = wx.BoxSizer(wx.HORIZONTAL)
        sizer_options.Add(sizer_buttons, 1, wx.EXPAND, 0)

        self.button_vector = wx.Button(self, wx.ID_ANY, _("Vectorize"))
        sizer_buttons.Add(self.button_vector, 0, 0, 0)

        label_spacer = wx.StaticText(self, wx.ID_ANY, " ")
        sizer_buttons.Add(label_spacer, 1, 0, 0)

        self.button_generate = wx.Button(self, wx.ID_ANY, _("Preview"))
        self.button_generate.SetToolTip(_("Generate a preview of the result"))
        sizer_buttons.Add(self.button_generate, 0, 0, 0)

        sizer_preview = StaticBoxSizer(self, wx.ID_ANY, _("Preview"), wx.VERTICAL)
        main_sizer.Add(sizer_preview, 2, wx.EXPAND, 0)

        self.bitmap_preview = wx.StaticBitmap(self, wx.ID_ANY, wx.NullBitmap)
        sizer_preview.Add(self.bitmap_preview, 1, wx.EXPAND, 0)

        self.vector_preview = wx.StaticBitmap(self, wx.ID_ANY, wx.NullBitmap)
        sizer_preview.Add(self.vector_preview, 1, wx.EXPAND, 0)

        self.SetSizer(main_sizer)
        main_sizer.Fit(self)

        # self._preview = True
        # self._need_updates = False

        # self.check_generate.SetValue(self._preview)

        self.wximage = wx.NullBitmap
        self.wxvector = wx.NullBitmap
        self._visible = False

        self.Layout()
        self.Centre()
        self.Bind(wx.EVT_BUTTON, self.on_button_create, self.button_vector)
        # self.Bind(wx.EVT_CHECKBOX, self.on_check_preview, self.check_generate)
        self.Bind(wx.EVT_BUTTON, self.on_changes, self.button_generate)
        self.Bind(wx.EVT_SIZE, self.on_size)
        # self.Bind(wx.EVT_SLIDER, self.on_changes, self.slider_alphamax)
        # self.Bind(wx.EVT_SLIDER, self.on_changes, self.slider_blacklevel)
        # self.Bind(wx.EVT_SLIDER, self.on_changes, self.slider_tolerance)
        # self.Bind(wx.EVT_SLIDER, self.on_changes, self.slider_turdsize)
        # self.Bind(wx.EVT_COMBOBOX, self.on_changes, self.combo_turnpolicy)
        # self.stop = None
        # self._update_thread = self.context.threaded(
        #         self.generate_preview, result=self.stop, daemon=True
        #     )

        self.set_widgets(node)

    # def on_check_preview(self, event):
    #     self._preview = self.check_generate.GetValue()

    def on_size(self, event):
        self.set_images(True)

    def pane_active(self):
        self._visible = True
        self.set_images(True)

    def pane_deactive(self):
        self._visible = False

    def on_changes(self, event):
        # self._need_updates = True
        self.generate_preview()

    def on_button_create(self, event):
        ipolicy = self.combo_turnpolicy.GetSelection()
        turnpolicy = self.turn_choices[ipolicy].lower()
        # slider 0 .. 10 translate to 0 .. 10
        turdsize = self.slider_turdsize.GetValue()
        # slider 0 .. 100 translate to 0 .. 1
        blacklevel = (
            self.slider_blacklevel.GetValue() / self.slider_blacklevel.GetMax() * 1.0
        )
        # slider 0 .. 150 translate to 0 .. 1.5
        opttolerance = (
            self.slider_tolerance.GetValue() / self.slider_tolerance.GetMax() * 1.5
        )
        # slider 0 .. 12 translate to 0 .. 1.333
        alphamax = (
            self.slider_alphamax.GetValue() / self.slider_alphamax.GetMax() * 4.0 / 3.0
        )
        opticurve = self.check_opticurve.GetValue()
        cmd = f"vectorize -z {turnpolicy} -t {turdsize} -a {alphamax}{' -n' if opticurve else ''} -O {opttolerance} -k {blacklevel}\n"
        self.context(cmd)

    def set_images(self, refresh=False):
        def opaque(source):
            img = source
            if img is not None:
                if img.mode == "RGBA":
                    r, g, b, a = img.split()
                    background = Image.new("RGB", img.size, "white")
                    background.paste(img, mask=a)
                    img = background
            return img

        if not self._visible:
            return
        if self.node is None or self.node.image is None:
            self.wximage = wx.NullBitmap
        else:
            if refresh:
                source_image = self.node.active_image
                source_image = opaque(source_image)
                pw, ph = self.bitmap_preview.GetSize()
                iw, ih = source_image.size
                wfac = pw / iw
                hfac = ph / ih
                # The smaller of the two decide how to scale the picture
                if wfac < hfac:
                    factor = wfac
                else:
                    factor = hfac
                # print (f"Window: {pw} x {ph}, Image= {iw} x {ih}, factor={factor:.3f}")
                if factor < 1.0:
                    image = source_image.resize((int(iw * factor), int(ih * factor)))
                else:
                    image = source_image
                self.wximage = self.img_2_wx(image)

        self.bitmap_preview.SetBitmap(self.wximage)

    def generate_preview(self):
        # from time import sleep
        make_vector = self.context.kernel.lookup("render-op/make_vector")
        make_raster = self.context.kernel.lookup("render-op/make_raster")
        # while self.alive:
        if not self._visible:
            return
        self.wxvector = wx.NullBitmap

        if self.node is not None and self.node.image is not None:
            matrix = self.node.matrix
            image = self.node.opaque_image
            ipolicy = self.combo_turnpolicy.GetSelection()
            # turnpolicy = self.turn_choices[ipolicy].lower()
            # slider 0 .. 10 translate to 0 .. 10
            turdsize = self.slider_turdsize.GetValue()
            # slider 0 .. 100 translate to 0 .. 1
            blacklevel = (
                self.slider_blacklevel.GetValue()
                / self.slider_blacklevel.GetMax()
                * 1.0
            )
            # slider 0 .. 150 translate to 0 .. 1.5
            opttolerance = (
                self.slider_tolerance.GetValue() / self.slider_tolerance.GetMax() * 1.5
            )
            # slider 0 .. 12 translate to 0 .. 1.333
            alphamax = (
                self.slider_alphamax.GetValue()
                / self.slider_alphamax.GetMax()
                * 4.0
                / 3.0
            )
            opticurve = self.check_opticurve.GetValue()
            bounds = self.node.paint_bounds
            if bounds is None:
                bounds = self.node.bounds
            if bounds is None:
                return
            xmin, ymin, xmax, ymax = bounds
            width = xmax - xmin
            height = ymax - ymin
            dpi = 500
            dots_per_units = dpi / UNITS_PER_INCH
            new_width = width * dots_per_units
            new_height = height * dots_per_units
            new_height = max(new_height, 1)
            new_width = max(new_width, 1)

            try:
                image = make_raster(
                    self.node,
                    bounds=bounds,
                    width=new_width,
                    height=new_height,
                )
                path = make_vector(
                    image=image,
                    interpolationpolicy=ipolicy,
                    turdsize=turdsize,
                    alphamax=alphamax,
                    opticurve=opticurve,
                    opttolerance=opttolerance,
                    blacklevel=blacklevel,
                )
            except:
                return
            path.transform *= Matrix(matrix)
            dummynode = PathNode(
                path=abs(path),
                stroke_width=0,
                stroke_scaled=False,
                fillrule=0,  # Fillrule.FILLRULE_NONZERO
            )
            if dummynode is None:
                return
            bounds = dummynode.paint_bounds
            if bounds is None:
                bounds = dummynode.bounds
            if bounds is None:
                return
            pw, ph = self.vector_preview.GetSize()
            iw, ih = self.node.image.size
            wfac = pw / iw
            hfac = ph / ih
            # The smaller of the two decide how to scale the picture
            if wfac < hfac:
                factor = wfac
            else:
                factor = hfac
            image = make_raster(
                dummynode,
                bounds,
                width=pw,
                height=ph,
                keep_ratio=True,
            )
            rw, rh = image.size
            # print (f"Area={pw}x{ph}, Org={iw}x{ih}, Raster={rw}x{rh}")
            # if factor < 1.0:
            #     image = image.resize((int(iw * factor), int(ih * factor)))
            self.wxvector = self.img_2_wx(image)

        self.vector_preview.SetBitmap(self.wxvector)

    @staticmethod
    def accepts(node):
        # Changing the staticmethod into a regular method will cause a crash
        # Not the nicest thing in the world, as we need to instantiate the class once to reset the status flag
        global HAS_VECTOR_ENGINE
        if node.type == "elem image" and HAS_VECTOR_ENGINE:
            return True
        return False

    def img_2_wx(self, image):
        width, height = image.size
        newimage = image.convert("RGB")
        return wx.Bitmap.FromBuffer(width, height, newimage.tobytes())

    def set_widgets(self, node=None):
        self.node = node
        if node is not None:
            self._need_updates = True
        self.set_images()

class ImagePropertyPanel(ScrolledPanel):
    def __init__(self, *args, context=None, node=None, **kwargs):
        # begin wxGlade: ConsolePanel.__init__
        kwargs["style"] = kwargs.get("style", 0) | wx.TAB_TRAVERSAL
        wx.Panel.__init__(self, *args, **kwargs)
        self.context = context
        self.node = node
        self.panel_id = IdPanel(
            self, id=wx.ID_ANY, context=self.context, node=self.node
        )

        self.text_dpi = TextCtrl(
            self,
            wx.ID_ANY,
            "500",
            style=wx.TE_PROCESS_ENTER,
            check="float",
            limited=True,
            nonzero=True,
        )
        self.check_prevent_crop = wx.CheckBox(self, wx.ID_ANY, _("No final crop"))

        self.panel_lock = PreventChangePanel(
            self, id=wx.ID_ANY, context=self.context, node=self.node
        )
        self.panel_xy = PositionSizePanel(
            self, id=wx.ID_ANY, context=self.context, node=self.node
        )

        self.panel_crop = CropPanel(
            self, id=wx.ID_ANY, context=self.context, node=self.node
        )
        self.check_enable_dither = wx.CheckBox(self, wx.ID_ANY, _("Dither"))
        self.choices = [
            "Floyd-Steinberg",
            "Atkinson",
            "Jarvis-Judice-Ninke",
            "Stucki",
            "Burkes",
            "Sierra3",
            "Sierra2",
            "Sierra-2-4a",
        ]
        self.combo_dither = wx.ComboBox(
            self,
            wx.ID_ANY,
            choices=self.choices,
            style=wx.CB_DROPDOWN,
        )
        # self.op_choices = []
        # self.image_ops = []
        # self.op_choices.append(_("Choose a script to apply"))
        # self.op_choices.append(_("Set to None"))
        # for op in list(self.context.elements.match("raster_script", suffix=True)):
        #     self.op_choices.append(_("Apply: {script}").format(script=op))
        #     self.image_ops.append(op)

        # self.combo_operations = wx.ComboBox(
        #     self,
        #     wx.ID_ANY,
        #     choices=self.op_choices,
        #     style=wx.CB_DROPDOWN,
        # )

        self.check_invert_grayscale = wx.CheckBox(self, wx.ID_ANY, _("Invert"))
        self.slider_grayscale_red = wx.Slider(
            self, wx.ID_ANY, 0, -1000, 1000, style=wx.SL_AUTOTICKS | wx.SL_HORIZONTAL
        )
        self.text_grayscale_red = wx.TextCtrl(self, wx.ID_ANY, "", style=wx.TE_READONLY)
        self.slider_grayscale_green = wx.Slider(
            self, wx.ID_ANY, 0, -1000, 1000, style=wx.SL_AUTOTICKS | wx.SL_HORIZONTAL
        )
        self.text_grayscale_green = wx.TextCtrl(
            self, wx.ID_ANY, "", style=wx.TE_READONLY
        )
        self.slider_grayscale_blue = wx.Slider(
            self, wx.ID_ANY, 0, -1000, 1000, style=wx.SL_AUTOTICKS | wx.SL_HORIZONTAL
        )
        self.text_grayscale_blue = wx.TextCtrl(
            self, wx.ID_ANY, "", style=wx.TE_READONLY
        )
        self.slider_grayscale_lightness = wx.Slider(
            self, wx.ID_ANY, 500, 0, 1000, style=wx.SL_AUTOTICKS | wx.SL_HORIZONTAL
        )
        self.text_grayscale_lightness = wx.TextCtrl(
            self, wx.ID_ANY, "", style=wx.TE_READONLY
        )

        self.__set_properties()
        self.__do_layout()

        self.Bind(wx.EVT_CHECKBOX, self.on_dither, self.check_enable_dither)
        self.Bind(wx.EVT_COMBOBOX, self.on_dither, self.combo_dither)
        # self.Bind(wx.EVT_COMBOBOX, self.on_combo_operation, self.combo_operations)

        self.Bind(wx.EVT_TEXT_ENTER, self.on_dither, self.combo_dither)

        self.text_dpi.SetActionRoutine(self.on_text_dpi)

        self.Bind(
            wx.EVT_CHECKBOX, self.on_check_invert_grayscale, self.check_invert_grayscale
        )
        self.Bind(
            wx.EVT_SLIDER,
            self.on_slider_grayscale_component,
            self.slider_grayscale_lightness,
        )
        self.Bind(
            wx.EVT_SLIDER, self.on_slider_grayscale_component, self.slider_grayscale_red
        )
        self.Bind(
            wx.EVT_SLIDER,
            self.on_slider_grayscale_component,
            self.slider_grayscale_green,
        )
        self.Bind(
            wx.EVT_SLIDER,
            self.on_slider_grayscale_component,
            self.slider_grayscale_blue,
        )
        self.check_prevent_crop.Bind(wx.EVT_CHECKBOX, self.on_crop_option)

        # self.check_enable_grayscale.SetValue(op["enable"])
        if node.invert is None:
            node.invert = False
        self.check_invert_grayscale.SetValue(node.invert)

        self.slider_grayscale_red.SetValue(int(node.red * 500.0))
        self.text_grayscale_red.SetValue(str(node.red))

        self.slider_grayscale_green.SetValue(int(node.green * 500.0))
        self.text_grayscale_green.SetValue(str(node.green))

        self.slider_grayscale_blue.SetValue(int(node.blue * 500.0))
        self.text_grayscale_blue.SetValue(str(node.blue))

        self.slider_grayscale_lightness.SetValue(int(node.lightness * 500.0))
        self.text_grayscale_lightness.SetValue(str(node.lightness))
        self.set_widgets()

    @staticmethod
    def accepts(node):
        if node.type == "elem image":
            return True
        return False

    def set_widgets(self, node=None):
        if node is None:
            node = self.node
        self.panel_id.set_widgets(node)
        self.panel_xy.set_widgets(node)
        self.panel_lock.set_widgets(node)
        self.panel_crop.set_widgets(node)
        self.node = node
        if node is None:
            return

        self.text_dpi.SetValue(str(node.dpi))
        self.check_enable_dither.SetValue(node.dither)
        self.combo_dither.SetValue(node.dither_type)
        self.check_prevent_crop.SetValue(node.prevent_crop)

    def __set_properties(self):
        self.check_prevent_crop.SetToolTip(_("Prevent final crop after all operations"))
        self.check_enable_dither.SetToolTip(_("Enable Dither"))
        self.check_enable_dither.SetValue(1)
        self.combo_dither.SetToolTip(_("Select dither algorithm to use"))
        self.combo_dither.SetSelection(0)
        # self.combo_operations.SetToolTip(_("Select image enhancement script to apply"))
        # self.combo_operations.SetSelection(0)
        self.check_invert_grayscale.SetToolTip(_("Invert Grayscale"))
        self.slider_grayscale_red.SetToolTip(_("Red component amount"))
        self.text_grayscale_red.SetToolTip(_("Red Factor"))
        self.slider_grayscale_green.SetToolTip(_("Green component control"))
        self.text_grayscale_green.SetToolTip(_("Green Factor"))
        self.slider_grayscale_blue.SetToolTip(_("Blue component control"))
        self.text_grayscale_blue.SetToolTip(_("Blue Factor"))
        self.slider_grayscale_lightness.SetToolTip(_("Lightness control"))
        self.text_grayscale_lightness.SetToolTip(_("Lightness"))
        # end wxGlade

    def __do_layout(self):
        # begin wxGlade: ImageProperty.__do_layout
        sizer_main = wx.BoxSizer(wx.VERTICAL)
        sizer_dim = wx.BoxSizer(wx.HORIZONTAL)
        sizer_xy = wx.BoxSizer(wx.HORIZONTAL)
        sizer_main.Add(self.panel_id, 0, wx.EXPAND, 0)
        sizer_main.Add(self.panel_crop, 0, wx.EXPAND, 0)

        sizer_dpi_dither = wx.BoxSizer(wx.HORIZONTAL)
        sizer_dpi = StaticBoxSizer(self, wx.ID_ANY, _("DPI:"), wx.HORIZONTAL)
        self.text_dpi.SetToolTip(_("Dots Per Inch"))
        sizer_dpi.Add(self.text_dpi, 1, wx.EXPAND, 0)

        sizer_dpi_dither.Add(sizer_dpi, 1, wx.EXPAND, 0)

        sizer_crop = StaticBoxSizer(self, wx.ID_ANY, _("Auto-Crop:"), wx.HORIZONTAL)
        sizer_crop.Add(self.check_prevent_crop, 1, wx.ALIGN_CENTER_VERTICAL, 0)

        sizer_dpi_dither.Add(sizer_crop, 1, wx.EXPAND, 0)

        sizer_dither = StaticBoxSizer(self, wx.ID_ANY, _("Dither"), wx.HORIZONTAL)
        sizer_dither.Add(self.check_enable_dither, 0, wx.ALIGN_CENTER_VERTICAL, 0)
        sizer_dither.Add(self.combo_dither, 0, wx.ALIGN_CENTER_VERTICAL, 0)

        sizer_dpi_dither.Add(sizer_dither, 2, wx.EXPAND, 0)

        sizer_main.Add(sizer_dpi_dither, 0, wx.EXPAND, 0)

        sizer_rg = wx.BoxSizer(wx.HORIZONTAL)
        sizer_bl = wx.BoxSizer(wx.HORIZONTAL)
        sizer_grayscale = StaticBoxSizer(self, wx.ID_ANY, _("Grayscale"), wx.VERTICAL)
        sizer_grayscale_lightness = StaticBoxSizer(
            self, wx.ID_ANY, _("Lightness"), wx.HORIZONTAL
        )
        sizer_grayscale_blue = StaticBoxSizer(self, wx.ID_ANY, _("Blue"), wx.HORIZONTAL)
        sizer_grayscale_green = StaticBoxSizer(
            self, wx.ID_ANY, _("Green"), wx.HORIZONTAL
        )
        sizer_grayscale_red = StaticBoxSizer(self, wx.ID_ANY, _("Red"), wx.HORIZONTAL)
        sizer_grayscale.Add(self.check_invert_grayscale, 0, 0, 0)
        sizer_grayscale_red.Add(self.slider_grayscale_red, 1, wx.EXPAND, 0)
        sizer_grayscale_red.Add(self.text_grayscale_red, 1, 0, 0)
        sizer_rg.Add(sizer_grayscale_red, 1, wx.EXPAND, 0)
        sizer_grayscale_green.Add(self.slider_grayscale_green, 1, wx.EXPAND, 0)
        sizer_grayscale_green.Add(self.text_grayscale_green, 1, 0, 0)
        sizer_rg.Add(sizer_grayscale_green, 1, wx.EXPAND, 0)
        sizer_grayscale_blue.Add(self.slider_grayscale_blue, 1, wx.EXPAND, 0)
        sizer_grayscale_blue.Add(self.text_grayscale_blue, 1, 0, 0)
        sizer_bl.Add(sizer_grayscale_blue, 1, wx.EXPAND, 0)
        sizer_grayscale_lightness.Add(self.slider_grayscale_lightness, 1, wx.EXPAND, 0)
        sizer_grayscale_lightness.Add(self.text_grayscale_lightness, 1, 0, 0)
        sizer_bl.Add(sizer_grayscale_lightness, 1, wx.EXPAND, 0)
        sizer_grayscale.Add(sizer_rg, 5, wx.EXPAND, 0)
        sizer_grayscale.Add(sizer_bl, 5, wx.EXPAND, 0)

        self.text_grayscale_red.SetMaxSize(dip_size(self, 70, -1))
        self.text_grayscale_green.SetMaxSize(dip_size(self, 70, -1))
        self.text_grayscale_blue.SetMaxSize(dip_size(self, 70, -1))
        self.text_grayscale_lightness.SetMaxSize(dip_size(self, 70, -1))

        sizer_main.Add(sizer_grayscale, 0, wx.EXPAND, 0)

        sizer_main.Add(self.panel_lock, 0, wx.EXPAND, 0)
        sizer_main.Add(self.panel_xy, 0, wx.EXPAND, 0)

        self.SetSizer(sizer_main)
        self.Layout()
        self.Centre()
        # end wxGlade

    def node_update(self):
        self.context.elements.emphasized()
        self.node.update(self.context)
        self.context.signal("element_property_update", self.node)

    def on_text_dpi(self):
        new_step = float(self.text_dpi.GetValue())
        self.node.dpi = new_step
        self.node_update()

    def on_crop_option(self, event):
        self.node.prevent_crop = self.check_prevent_crop.GetValue()
        self.node_update()

    def on_dither(self, event=None):
        # Dither can be set by two different means:
        # a. directly
        # b. via a script
        dither_op = None
        for op in self.node.operations:
            if op["name"] == "dither":
                dither_op = op
                break
        dither_flag = self.check_enable_dither.GetValue()
        dither_type = self.choices[self.combo_dither.GetSelection()]
        if dither_op is not None:
            dither_op["enable"] = dither_flag
            dither_op["type"] = dither_type
        self.node.dither = dither_flag
        self.node.dither_type = dither_type
        self.node_update()

    def on_check_invert_grayscale(
        self, event=None
    ):  # wxGlade: RasterWizard.<event_handler>
        self.node.invert = self.check_invert_grayscale.GetValue()
        self.node_update()

    def on_slider_grayscale_component(
        self, event=None
    ):  # wxGlade: GrayscalePanel.<event_handler>
        self.node.red = float(int(self.slider_grayscale_red.GetValue()) / 500.0)
        self.text_grayscale_red.SetValue(str(self.node.red))

        self.node.green = float(int(self.slider_grayscale_green.GetValue()) / 500.0)
        self.text_grayscale_green.SetValue(str(self.node.green))

        self.node.blue = float(int(self.slider_grayscale_blue.GetValue()) / 500.0)
        self.text_grayscale_blue.SetValue(str(self.node.blue))

        self.node.lightness = float(
            int(self.slider_grayscale_lightness.GetValue()) / 500.0
        )
        self.text_grayscale_lightness.SetValue(str(self.node.lightness))
        self.node_update()

    # def on_combo_operation(self, event):
    #     idx = self.combo_operations.GetSelection()
    #     if idx <= 0:
    #         return
    #     elif idx == 1:
    #         self.node.operations = []
    #     else:
    #         script = self.image_ops[idx - 2]
    #         raster_script = self.context.lookup(f"raster_script/{script}")
    #         if raster_script is None:
    #             return
    #         self.node.operations = raster_script
    #     self.node.update(self.context)
    #     self.context.signal("element_property_reload", self.node)
    #     self.context.signal("propupdate", self.node)

# class ImageProperty(MWindow):
#     def __init__(self, *args, node=None, **kwds):
#         super().__init__(276, 218, *args, **kwds)
#         self.panels = []
#         main_sizer = wx.BoxSizer(wx.VERTICAL)
#         panel_main = ImagePropertyPanel(
#             self, wx.ID_ANY, context=self.context, node=node
#         )
#         panel_modify = ImageModificationPanel(
#             self, wx.ID_ANY, context=self.context, node=node
#         )
#         panel_vector = ImageVectorisationPanel(
#             self, wx.ID_ANY, context=self.context, node=node
#         )
#         notebook_main = wx.aui.AuiNotebook(
#             self,
#             -1,
#             style=wx.aui.AUI_NB_TAB_EXTERNAL_MOVE
#             | wx.aui.AUI_NB_SCROLL_BUTTONS
#             | wx.aui.AUI_NB_TAB_SPLIT
#             | wx.aui.AUI_NB_TAB_MOVE,
#         )
#         notebook_main.AddPage(panel_main, _("Properties"))
#         notebook_main.AddPage(panel_modify, _("Modification"))
#         notebook_main.AddPage(panel_vector, _("Vectorisation"))

#         self.panels.append(panel_main)
#         self.panels.append(panel_modify)
#         self.panels.append(panel_vector)
#         for panel in self.panels:
#             self.add_module_delegate(panel)
#         # begin wxGlade: ImageProperty.__set_properties
#         _icon = wx.NullIcon
#         _icon.CopyFromBitmap(icons8_image_50.GetBitmap())
#         self.SetIcon(_icon)
#         self.SetTitle(_("Image Properties"))
#         main_sizer.Add(notebook_main, 1, wx.EXPAND, 0)
#         self.SetSizer(main_sizer)
#         self.Layout()

#     def restore(self, *args, node=None, **kwds):
#         for panel in self.panels:
#             panel.set_widgets(node)

#     def window_preserve(self):
#         return False

#     def window_menu(self):
#         return False


# -*- coding: utf-8 -*-
"""
Created on Thu Dec 13 12:53:59 2018

@author: Grani
"""

import requests

phonebook = {
        "Dani": "79060485763"
}

localhost = "http://127.0.0.1:5000"

def show_phonebook():
    response = requests.get(localhost + "/phonebook")
    if response.status_code == 200:
        return response.json()
    else:
        return "I'm calling police"

def add_contact(name, phone):
    response = requests.post(localhost + "/addcontact/" + name + "/" + phone)
    if response.status_code == 200:
        return response.json()
    else:
        return "I'm calling police"

def show_contact(name):
    response = requests.get(localhost + "/showcontact/" + name)
    if response.status_code == 200:
        return response.json()
    else:
        return "I'm calling police"

def del_contact(name):
    response = requests.post(localhost + "/removecontact/" + name)
    if response.status_code == 200:
        return response.json()
    else:
        return "I'm calling police"

def update_contact(name, phone):
    response = requests.post(localhost + "/updatecontact/" + name + "/" + phone)
    if response.status_code == 200:
        return response.json()
    else:
        return "I'm calling police"


def get_data():
    with open("Day3.txt","r") as file:
        return file.readline()

def checkHouse(pos, visited):
    if (pos[0],pos[1]) in visited:
        visited[pos[0],pos[1]] += 1
    else:
        visited[pos[0],pos[1]] = 1

def nextMove(pos, move):
    if move == '^':
        pos[1] += 1
    elif move == 'v':
        pos[1] -= 1
    elif move == '>':
        pos[0] += 1
    else:
        pos[0] -= 1
    return pos

def task1(data):
    pos = [0,0]
    visited = {}
    checkHouse(pos, visited)
    for move in data:
        pos = nextMove(pos, move)
        checkHouse(pos, visited)
    print("Task 1: Unique Houses Visited: {}".format(len(visited)))

def task2(data):
    santaPos = [0,0]
    roboPos = [0,0]
    visited = {}
    checkHouse(santaPos,visited)

    for x in range(len(data)):
        if x%2 != 0:
            roboPos = nextMove(roboPos,data[x])
            checkHouse(roboPos,visited)
        else:
            santaPos = nextMove(santaPos,data[x])
            checkHouse(santaPos,visited)

    print("Task 2: Unique Houses Visited With Robo Santa: {}".format(len(visited)))

def main():
    data = list(get_data())
    task1(data)
    task2(data)

if __name__ == "__main__":
    main()


from array import*
arr1 = array('i',[1,2,3,4,5,6,7,8])

def searchInArray(array, value):
    for i in array:
        if i == value:
            return array.index(value)
    return "the element does not exist in this array"

print(searchInArray(arr1,1))


# -*- coding: latin-1 -*-
import serial
import time

channel = 'COM3'

arduino = serial.Serial(channel, 9600, timeout=1)  # Creates arduino object
time.sleep(3)

def turn_left(servoNumber):
    toLeft = str(servoNumber) + ' l'
    arduino.write(bytes(toLeft, 'latin-1'))

def turn_right(servonumber):
    toRight = str(servonumber) + ' l'
    arduino.write(bytes(toRight, 'latin-1'))

while True:
    command = input("What servo and left or right (1/2 l/r: ")
    # print(bytes(command, "latin-1"))
    # sends command to arduino via serial port
    if command[0] == 1:
        if command[2] == 'l':
            turn_left(1)

        if command[2] == 'r':
            turn_right(1)

    if command[0] == 2:
        if command[2] == 'l':
            turn_left(2)

        if command[2] == 'r':
            turn_right(2)

    value = arduino.readlines()  # reads all lines sent from arduino via Serial.print()
    for line in value:
        print(line.decode(encoding='latin-1'))

    if command == 'q':
        break


# -*- coding: utf-8 -*-
from .__conversion import _point_to_radians, _point_to_degrees, _radians_to_degrees, \
    _degrees_to_radians
from .__error_checking import _error_check_point
from ._constants import *
from math import atan
from math import atan2
from math import cos
from math import radians
from math import sin
from math import sqrt
from math import tan

def distance_between_points(p1, p2, unit='meters', haversine=True, vincenty=False):
    """ This function computes the distance between two points in the unit given in the unit parameter.  It will
    calculate the distance using the haversine unless the user specifies haversine to be False.  Then law of cosines
    will be used
    :param p1: tuple point of (lon, lat)
    :param p2: tuple point of (lon, lat)
    :param unit: unit of measurement. List can be found in constants.eligible_units
    :param haversine: True (default) uses haversine distance, False uses law of cosines
    :param vincenty: False (default) uses vincenty distance, False haversine
    :return: Distance between p1 and p2 in the units specified.
    """
    lon1, lat1 = _point_to_radians(_error_check_point(p1))
    lon2, lat2 = _point_to_radians(_error_check_point(p2))
    r_earth = getattr(radius_earth, unit, 'meters')

    if vincenty:  # from https://nathanrooy.github.io/posts/2016-12-18/vincenty-formula-with-python/

        maxIter=200
        tol=10**-12
        a=6378137.0                             # radius at equator in meters (WGS-84)
        f=1/298.257223563                       # flattening of the ellipsoid (WGS-84)
        b=(1-f)*a

        phi_1,L_1,=[lon1, lat1]                       # (lat=L_?,lon=phi_?)
        phi_2,L_2,=[lon2, lat2]

        u_1=atan((1-f)*tan(radians(phi_1)))
        u_2=atan((1-f)*tan(radians(phi_2)))

        L=radians(L_2-L_1)

        Lambda=L                                # set initial value of lambda to L

        sin_u1=sin(u_1)
        cos_u1=cos(u_1)
        sin_u2=sin(u_2)
        cos_u2=cos(u_2)

        #--- BEGIN ITERATIONS -----------------------------+
        iters=0
        for i in range(0,maxIter):
            iters+=1

            cos_lambda=cos(Lambda)
            sin_lambda=sin(Lambda)
            sin_sigma=sqrt((cos_u2*sin(Lambda))**2+(cos_u1*sin_u2-sin_u1*cos_u2*cos_lambda)**2)
            cos_sigma=sin_u1*sin_u2+cos_u1*cos_u2*cos_lambda
            sigma=atan2(sin_sigma,cos_sigma)
            sin_alpha=(cos_u1*cos_u2*sin_lambda)/sin_sigma
            cos_sq_alpha=1-sin_alpha**2
            cos2_sigma_m=cos_sigma-((2*sin_u1*sin_u2)/cos_sq_alpha)
            C=(f/16)*cos_sq_alpha*(4+f*(4-3*cos_sq_alpha))
            Lambda_prev=Lambda
            Lambda=L+(1-C)*f*sin_alpha*(sigma+C*sin_sigma*(cos2_sigma_m+C*cos_sigma*(-1+2*cos2_sigma_m**2)))

            # successful convergence
            diff=abs(Lambda_prev-Lambda)
            if diff<=tol:
                break

        u_sq=cos_sq_alpha*((a**2-b**2)/b**2)
        A=1+(u_sq/16384)*(4096+u_sq*(-768+u_sq*(320-175*u_sq)))
        B=(u_sq/1024)*(256+u_sq*(-128+u_sq*(74-47*u_sq)))
        delta_sig=B*sin_sigma*(cos2_sigma_m+0.25*B*(cos_sigma*(-1+2*cos2_sigma_m**2)-(1/6)*B*cos2_sigma_m*(-3+4*sin_sigma**2)*(-3+4*cos2_sigma_m**2)))

        return b*A*(sigma-delta_sig) # output distance in meters

    if haversine:
        # Haversine
        d_lat, d_lon = lat2 - lat1, lon2 - lon1
        a = sin(d_lat / 2) * sin(d_lat / 2) + cos(lat1) * \
            cos(lat2) * sin(d_lon / 2) * sin(d_lon / 2)
        c = 2 * atan2(sqrt(a), sqrt((1 - a)))
        dist = r_earth * c
        return dist
    # Spherical Law Of Cosines
    dist = acos(sin(lat1) * sin(lat2) + cos(lat1) *
                cos(lat2) * cos(lon2 - lon1)) * r_earth
    return dist

def bearing_at_p1(p1, p2):
    """ This function computes the bearing (i.e. course) at p1 given a destination of p2.  Use in conjunction with
    midpoint(*) and intermediate_point(*) to find the course along the route.  Use bearing_at_p2(*) to find the bearing
    at the endpoint
    :param p1: tuple point of (lon, lat)
    :param p2: tuple point of (lon, lat)
    :return: Course, in degrees
    """
    lon1, lat1 = _point_to_radians(_error_check_point(p1))
    lon2, lat2 = _point_to_radians(_error_check_point(p2))
    x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(lon2 - lon1)
    y = sin(lon2 - lon1) * cos(lat2)
    course = atan2(y, x)
    return _radians_to_degrees(course)

def bearing_at_p2(p1, p2):
    """ This function computes the bearing (i.e. course) at p2 given a starting point of p1.  Use in conjunction with
    midpoint(*) and intermediate_point(*) to find the course along the route.  Use bearing_at_p1(*) to find the bearing
    at the endpoint
    :param p1: tuple point of (lon, lat)
    :param p2: tuple point of (lon, lat)
    :return: Course, in degrees
    """
    return (bearing_at_p1(p2, p1) + 180) % 360

def midpoint(p1, p2):
    """ This is the half-way point along a great circle path between the two points.
    :param p1: tuple point of (lon, lat)
    :param p2: tuple point of (lon, lat)
    :return: point (lon, lat)
    """
    lon1, lat1 = _point_to_radians(_error_check_point(p1))
    lon2, lat2 = _point_to_radians(_error_check_point(p2))
    b_x = cos(lat2) * cos(lon2 - lon1)
    b_y = cos(lat2) * sin(lon2 - lon1)
    lat3 = atan2(sin(lat1) + sin(lat2), sqrt((cos(lat1) + b_x)
                                             * (cos(lat1) + b_x) + b_y * b_y))
    lon3 = lon1 + atan2(b_y, cos(lat1) + b_x)
    lat3 = _radians_to_degrees(lat3)
    lon3 = (_radians_to_degrees(lon3) + 540) % 360 - 180
    p3 = (lon3, lat3)
    return p3

def intermediate_point(p1, p2, fraction=0.5):
    """ This function calculates the intermediate point along the course laid out by p1 to p2.  fraction is the fraction
    of the distance between p1 and p2, where 0 is p1, 0.5 is equivalent to midpoint(*), and 1 is p2.
    :param p1: tuple point of (lon, lat)
    :param p2: tuple point of (lon, lat)
    :param fraction: the fraction of the distance along the path.
    :return: point (lon, lat)
    """
    lon1, lat1 = _point_to_radians(_error_check_point(p1))
    lon2, lat2 = _point_to_radians(_error_check_point(p2))
    delta = distance_between_points(p1, p2, vincenty=False) / radius_earth.meters
    a = sin((1 - fraction) * delta) / sin(delta)
    b = sin(fraction * delta) / sin(delta)
    x = a * cos(lat1) * cos(lon1) + b * cos(lat2) * cos(lon2)
    y = a * cos(lat1) * sin(lon1) + b * cos(lat2) * sin(lon2)
    z = a * sin(lat1) + b * sin(lat2)
    lat3 = atan2(z, sqrt(x * x + y * y))
    lon3 = atan2(y, x)
    return _point_to_degrees((lon3, lat3))

def point_given_start_and_bearing(p1, course, distance, unit='meters'):
    """ Given a start point, initial bearing, and distance, this will calculate the destination point and final
    bearing travelling along a (shortest distance) great circle arc.
    :param p1: tuple point of (lon, lat)
    :param course: Course, in degrees
    :param distance: a length in unit
    :param unit: unit of measurement. List can be found in constants.eligible_units
    :return: point (lon, lat)
    """
    lon1, lat1 = _point_to_radians(_error_check_point(p1))
    brng = _degrees_to_radians(course)
    r_earth = getattr(radius_earth, unit, 'meters')
    delta = distance / r_earth
    lat2 = asin(sin(lat1) * cos(delta) + cos(lat1) * sin(delta) * cos(brng))
    lon2 = lon1 + atan2(sin(brng) * sin(delta) * cos(lat1),
                        cos(delta) - sin(lat1) * sin(lat2))
    lon2 = (_radians_to_degrees(lon2) + 540) % 360 - 180
    p2 = (lon2, _radians_to_degrees(lat2))
    return p2


from needs import es, request, Resource, Namespace, timefunc, lastPath, callWazuhApi, wazuhlogin

wazuh = Namespace(
    name="wazuh", description="get wazuh db!")

@wazuh.route('/agents')
class userlist(Resource):
    def get(self):
        """get Agent info => ip,name,status!"""
        wazuhlogin()
        agents = []
        for r in callWazuhApi("/agents")["data"]["affected_items"]:
            # Manage PC는 제외
            if r["id"] == '000' or r["status"] == "never_connected":
                continue
            agents.append(
                {"name": r["name"], "ip": r["ip"], "status": r["status"]})
        return agents


import os
import sys
import numpy as np
import pandas as pd
from scipy import interp
from sklearn import metrics
import math
from sklearn.inspection import permutation_importance

# Model 폴더 찾기 위한 방법
pathA = os.path.join(os.path.dirname(__file__), os.path.pardir, "../../../")
pathB = os.path.join(os.path.join(pathA))
basePath = os.path.abspath(pathB)
# Model Path 등록

sys.path.append(basePath)

from Common.Logger.Logger import logger

log = logger("log")

class graph:
    def __init__(self, param, classes=None):
        self.classes = classes
        self.nClasses = 0
        self.param = param
        if classes is not None:
            self.nClasses = len(self.classes)

    # input : yTest, yPred
    # output : output
    # Check : yPred는 decision_function으로 계산해야 함. smpark
    def roc(self, yTest, yPred):
        fpr = dict()
        tpr = dict()
        roc_auc = dict()

        # for i in range(self.nClasses):
        #     try:
        #         fpr[i], tpr[i], _ = metrics.roc_curve(yTest[:, i], yPred[:, i])
        #         roc_auc[i] = metrics.auc(fpr[i], tpr[i])
        #     except:
        #         continue

        fpr["micro"], tpr["micro"], _ = metrics.roc_curve(yTest.ravel(), yPred.ravel())
        roc_auc["micro"] = metrics.auc(fpr["micro"], tpr["micro"])

        # all_fpr = np.unique(np.concatenate([fpr[i] for i in range(self.nClasses)]))

        # Then interpolate all ROC curves at this points
        # mean_tpr = np.zeros_like(all_fpr)
        # for i in range(self.nClasses):
            # mean_tpr += interp(all_fpr, fpr[i], tpr[i])

        # Finally average it and compute AUC
        # mean_tpr /= self.nClasses

        # fpr["macro"] = all_fpr
        # tpr["macro"] = mean_tpr
        # roc_auc["macro"] = metrics.auc(fpr["macro"], tpr["macro"])

        microROCPosition = []
        for i in range(len(fpr['micro'])):
            # if (not math.isnan(float(fpr['micro'][i]))) and (not math.isnan(float(tpr['micro'][i]))):
            microROCPosition.append(
                {
                    "X": '{:.4f}'.format(fpr['micro'][i]),
                    "Y": '{:.4f}'.format(tpr['micro'][i])
                }
            )

        # macroROCPosition = []
        # for i in range(len(fpr['macro'])):
        #     if (not math.isnan(float(fpr['macro'][i]))) and (not math.isnan(float(tpr['macro'][i]))):
        #         macroROCPosition.append(
        #             {
        #                 "X": '{:.4f}'.format(fpr['macro'][i]),
        #                 "Y": '{:.4f}'.format(tpr['macro'][i])
        #             }
        #         )

        output = {
            "AI_CD": self.param["SERVER_PARAM"]["AI_CD"] if "SERVER_PARAM" in self.param else None,
            "MDL_IDX": self.param["MDL_IDX"] if "MDL_IDX" in self.param else None,
            "MODEL_NAME": self.param["MODEL_NAME"] if "MODEL_NAME" in self.param else None,

            "GRAPH_TYPE": "ROC_Curve",
            "LEGEND_X": "False Positive Rate",
            "LEGEND_Y": "True Positive Rate",
            "GRAPH_DATA": [
                {
                    "GRAPH_NAME": "ROC Curve",
                    "AREA": '{:.4f}'.format(roc_auc["micro"]),
                    "GRAPH_POSITION": microROCPosition,
                }
                # {
                #     "GRAPH_NAME": "Macro Average ROC Curve",
                #     "AREA": '{:.4f}'.format(roc_auc["micro"]),
                #     "GRAPH_POSITION": macroROCPosition,
                # }
            ]
        }

        # for i in range(self.nClasses):
        #     ROCPositionClsses = []
        #     for j in range(len(fpr[i])):
        #         if (not math.isnan(float(fpr[i][j]))) and (not math.isnan(float(tpr[i][j]))):
        #             ROCPositionClsses.append(
        #                 {
        #                     "X": '{:.4f}'.format(fpr[i][j]),
        #                     "Y": '{:.4f}'.format(tpr[i][j])
        #                 }
        #             )

        #     output["GRAPH_DATA"].append(
        #         {
        #             "GRAPH_NAME": "ROC Curve of class {}".format(self.classes[i]),
        #             "AREA": '{:.4f}'.format(roc_auc[i]),
        #             "GRAPH_POSITION": ROCPositionClsses,
        #         }
        #     )

        return output

    # input : yTest, yPred
    # output : output
    # Check : yPred는 predict_proba로 계산해야 함. smpark
    def precisionRecall(self, yTest, yPred):
        precision = dict()
        recall = dict()
        average_precision = dict()
        graphData = []

        precision["micro"], recall["micro"], _ = metrics.precision_recall_curve(yTest.ravel(), yPred.ravel())
        average_precision["micro"] = metrics.average_precision_score(yTest, yPred, average="micro")

        microPRPosition = []
        for i in range(len(precision['micro'])):
            # if (not math.isnan(float(precision['micro'][i]))) and (not math.isnan(float(recall['micro'][i]))):
            microPRPosition.append(
                {
                    "X": '{:.4f}'.format(recall['micro'][i]),
                    "Y": '{:.4f}'.format(precision['micro'][i])
                }
            )

        # for i in range(self.nClasses):
        #     preRecallPosition = []
        #     precision[i], recall[i], _ = metrics.precision_recall_curve(
        #         yTest[:, i],
        #         yPred[:, i]
        #     )

        #     for j in range(len(precision[i])):
        #         if (not math.isnan(float(precision[i][j]))) and (not(math.isnan(float(recall[i][j])))):
        #             preRecallPosition.append(
        #                 {
        #                     "X": '{:.4f}'.format(recall[i][j]),
        #                     "Y": '{:.4f}'.format(precision[i][j])
        #                 }
        #             )

        #     graphData.append({
        #         "GRAPH_NAME": "Precision Recall Curve of class {}".format(self.classes[i]),
        #         "GRAPH_POSITION": preRecallPosition,
        #     })

        # output = {
        #     "AI_CD": self.param["SERVER_PARAM"]["AI_CD"],
        #     "MDL_IDX": self.param["MDL_IDX"],
        #     "MODEL_NAME": self.param["MODEL_NAME"],
        #     "GRAPH_TYPE": "Precision_Recall_Curve",
        #     "LEGEND_X": "Recall",
        #     "LEGEND_Y": "Precision",
        #     "GRAPH_DATA": graphData
        # }

        output = {
            "AI_CD": self.param["SERVER_PARAM"]["AI_CD"] if "SERVER_PARAM" in self.param else None,
            "MDL_IDX": self.param["MDL_IDX"] if "MDL_IDX" in self.param else None,
            "MODEL_NAME": self.param["MODEL_NAME"] if "MODEL_NAME" in self.param else None,

            "GRAPH_TYPE": "Precision_Recall_Curve",
            "LEGEND_X": "recall",
            "LEGEND_Y": "precision",
            "GRAPH_DATA": [
                {
                    "GRAPH_NAME": "PR Curve",
                    "AREA": '{:.4f}'.format(average_precision["micro"]),
                    "GRAPH_POSITION": microPRPosition,
                }
            ]
        }

        return output

    # input : yTest, yPred
    # output : output
    # Check : yPred는 predict로 계산해야 함. smpark
    # Check2 : yTest, yPred 둘 다 binarize -> decode 필요함(1차원으로 만들어 줘야함)
    def confusionMatrix(self, yTest, yPred, classes):

        if type(yTest[0]) != str:
            labels = range(0, len(classes))
        else:
            labels = classes

        cm = metrics.confusion_matrix(yTest, yPred, normalize="all", labels=labels)
        cm = cm.tolist()
        newCm = []

        for data in cm:
            tmpCm = []
            for tmp in data:
                tmpCm.append(float('{:.4f}'.format(tmp)))
            newCm.append(tmpCm)

        output = {
            "AI_CD": self.param["SERVER_PARAM"]["AI_CD"] if "SERVER_PARAM" in self.param else None,
            "MDL_IDX": self.param["MDL_IDX"] if "MDL_IDX" in self.param else None,
            "MODEL_NAME": self.param["MODEL_NAME"] if "MODEL_NAME" in self.param else None,
            "GRAPH_TYPE": "Confusion_Matrix",
            "LEGEND_X": self.classes,
            "LEGEND_Y": self.classes,
            "GRAPH_DATA": [{
                "GRAPH_NAME": "Confusion Matrix",
                "GRAPH_POSITION": newCm,
            }]
        }
        return output

    # input : yTest, yPred
    # output : output
    # Check : yPred 는 Predict로 계산해야 함. smpark
    def regPlot(self, yTest, yPred):

        graphData = []

        m, b = np.polyfit(yTest, yPred, 1)

        pt = m * yTest + b

        xMin = min(yTest)
        xMax = max(yTest)

        yMin = min(pt)
        yMax = max(pt)

        for i in range(len(yTest)):
            if (not math.isnan(float(yTest[i]))) and (not math.isnan(float(yPred[i]))):
                graphData.append(
                    {
                        "X": '{:.4f}'.format(yTest[i]),
                        "Y": '{:.4f}'.format(yPred[i])
                    }
                )
        output = {
            "AI_CD": self.param["SERVER_PARAM"]["AI_CD"] if "SERVER_PARAM" in self.param else None,
            "MDL_IDX": self.param["MDL_IDX"] if "MDL_IDX" in self.param else None,
            "MODEL_NAME": self.param["MODEL_NAME"] if "MODEL_NAME" in self.param else None,
            "GRAPH_TYPE": "Reg_Plot",
            "LEGEND_X": "Y Label",
            "LEGEND_Y": "Y Pred",
            "BASE_GRAPH_DATA": [
                {
                    "X": '{:.4f}'.format(xMin),
                    "Y": '{:.4f}'.format(yMin)
                },
                {
                    "X": '{:.4f}'.format(xMax),
                    "Y": '{:.4f}'.format(yMax)
                }
            ],
            "GRAPH_DATA": [{
                "GRAPH_NAME": "Reg Plot",
                "GRAPH_POSITION": graphData,
            }]
        }
        return output

    def featureImportance(self, colNames, modelFi):

        fImp = list(modelFi)
        fImpAll = 0
        featureImp = []

        for i in range(len(colNames)):
            fImpAll += fImp[i]

        for j in range(len(fImp)):
            fImp[j] = float(fImp[j]/fImpAll)

        dictFi = dict(zip(colNames, fImp))
        dictFi = sorted(dictFi.items(), reverse=True, key=lambda item: item[1])

        for k in range(len(dictFi)):
            featureImp.append({
                    "name": '{}'.format(dictFi[k][0]),
                    "value": '{:.4f}'.format(dictFi[k][1])
            })

        output = {
            "AI_CD": self.param["SERVER_PARAM"]["AI_CD"] if "SERVER_PARAM" in self.param else None,
            "MDL_IDX": self.param["MDL_IDX"] if "MDL_IDX" in self.param else None,
            "MODEL_NAME": self.param["MODEL_NAME"] if "MODEL_NAME" in self.param else None,
            "GRAPH_TYPE": "Feature_Importance",
            "GRAPH_DATA": [{
                "GRAPH_NAME": "Feature Importance",
                "GRAPH_POSITION": featureImp,
            }]
        }
        return output

    def permutation_fi(self, model, xTest, yTest, colNames, n_repeats=None):

        pImp = permutation_importance(model, xTest, yTest, n_repeats=n_repeats, random_state=42)
        pImp_mean = pImp.importances_mean
        pImpAll = 0

        permutationImp = []
        for i in range(len(colNames)):
            if pImp_mean[i] < 0:
                pImp_mean[i] = 0.0
            pImpAll += pImp_mean[i]

        for j in range(len(pImp_mean)):
            pImp_mean[j] = float(pImp_mean[j]/pImpAll)

        dictPi = dict(zip(colNames, pImp_mean))
        dictPi = sorted(dictPi.items(), reverse=True, key=lambda item: item[1])

        for k in range(len(dictPi)):
            permutationImp.append({
                    "name": '{}'.format(dictPi[k][0]),
                    "value": '{:.4f}'.format(dictPi[k][1])
            })

        output = {
            "AI_CD": self.param["SERVER_PARAM"]["AI_CD"] if "SERVER_PARAM" in self.param else None,
            "MDL_IDX": self.param["MDL_IDX"] if "MDL_IDX" in self.param else None,
            "MODEL_NAME": self.param["MODEL_NAME"] if "MODEL_NAME" in self.param else None,
            "GRAPH_TYPE": "Feature_Importance",
            "GRAPH_DATA": [{
                "GRAPH_NAME": "Feature Importance",
                "GRAPH_POSITION": permutationImp,
            }]
        }

        return output

    # input : yTest, yPred
    # output : output
    # Check : yPred 는 Predict로 계산해야 함. smpark
    # Check2 : yTest는 numpy array로 와야함.
    # 고려사항 : 샘플링 필요할 수 있음.
    def distributionPlot(self, yTest, yPred):
        yTestGraphData = []
        yPredGraphData = []
        graphData = []

        for i in range(len(yTest)):
            if not math.isnan(float(yTest[i])):
                yTestGraphData.append(
                    {
                        "X": '{:.4f}'.format(i),
                        "Y": '{:.4f}'.format(yTest[i]),
                    }
                )

        graphData.append(
            {
                "GRAPH_NAME": "Distribution Plot of Y Test Data",
                "GRAPH_POSITION": yTestGraphData,
            }
        )

        for i in range(len(yPred)):
            if not math.isnan(float(yPred[i])):
                yPredGraphData.append(
                    {
                        "X": '{:.4f}'.format(i),
                        "Y": '{:.4f}'.format(yPred[i]),
                    }
                )

        graphData.append(
            {
                "GRAPH_NAME": "Distribution Plot of Y Pred Data",
                "GRAPH_POSITION": yPredGraphData,
            }
        )

        output = {
            "AI_CD": self.param["SERVER_PARAM"]["AI_CD"] if "SERVER_PARAM" in self.param else None,
            "MDL_IDX": self.param["MDL_IDX"] if "MDL_IDX" in self.param else None,
            "MODEL_NAME": self.param["MODEL_NAME"] if "MODEL_NAME" in self.param else None,
            "GRAPH_TYPE": "Distribution_Plot",
            "LEGEND_X": "Amount of Test Dataset",
            "LEGEND_Y": "Y Test and Y Pred",
            "GRAPH_DATA": graphData
        }
        return output


"""
Make a class called User. Create two attributes called first_name and last_name,
and then create several other attributes that are typically stored in a user profile.
Make a method called describe_user() that prints a summary of the user’s information.
Make another method called greet_user() that prints a personalized greeting to the user.
Create several instances representing different users, and call both methods for each user.
"""

from chapter_nine.user import User

user1 = User('naman', 'kumar', '1234', 'ghaziabad')
user1.describe_user(User.user_details, 0)
user1.greet_user()

user2 = User('kumar', 'sambhav', '5678', 'gurugram')
user2.describe_user(User.user_details, 1)
user2.greet_user()


import copy

import pulp
from scipy import spatial
from tqdm import tqdm, trange
import numpy as np

from file_readers import read_fire_stations_file, read_waypoint_file
from utilities import _convert_df_to_alternative_data_structure, parfile_reader

def multi_knapsack_min_packing_covering_solver(waypoints, fire_stations, max_service_time_hr, pars, exact=False):
    max_service_time = max_service_time_hr * 60 * 60 * pars["drone_speed_mps"]
    stations = _convert_df_to_alternative_data_structure(fire_stations)
    stations = {k: v['point'] for k, v in stations.items()}
    station_keys, station_points = zip(*stations.items())
    waypoints = list(waypoints)
    d_matrix = spatial.distance_matrix(x=station_points, y=waypoints)
    solution = multi_knapsack_min_packing_covering_heuristic(d_matrix, len(stations),
                                                             len(waypoints), max_service_time)
    if exact:
        solution = multi_knapsack_min_packing_covering_exact(station_keys, waypoints,
                                                             d_matrix, max_service_time,
                                                             warm_starter=solution)
    else:
        solution = {station_keys[idx] for idx in solution}
    return solution, stations

def multi_knapsack_min_packing_covering_heuristic(d_matrix, n_depots, n_wp, max_service_time):
    solution = dict()
    d_matrix_copy = copy.deepcopy(d_matrix)
    depots = list(range(n_depots))
    wp = set(range(n_wp))
    p_bar = tqdm(
        desc="Finding Heuristic Solution", postfix=dict(N_Depot=len(solution), N_WP=len(wp)))
    while len(wp) > 0:
        _depot, _wp = np.where(d_matrix_copy == np.amin(d_matrix_copy))
        _depot, _wp = int(_depot[0]), int(_wp[0])
        solution[_depot] = [_wp]
        wp.discard(_wp)
        d_matrix_copy[:, _wp] = np.inf
        dists_from_depot = d_matrix_copy[_depot]
        while sum(d_matrix[_depot, j] for j in solution[_depot]) < max_service_time and len(wp) > 0:
            _wp = np.where(dists_from_depot == np.amin(dists_from_depot))
            _wp = int(_wp[0])
            if _wp == 102:
                print()
            if _wp not in wp:
                dists_from_depot[_wp] = np.inf
                d_matrix_copy[_depot, _wp] = np.inf
                continue
            if sum(d_matrix[_depot, j] for j in solution[_depot]) + d_matrix[_depot, _wp] > max_service_time:
                break
            solution[_depot].append(_wp)
            dists_from_depot[_wp] = np.inf
            d_matrix_copy[:, _wp] = np.inf
            wp.discard(_wp)
        p_bar.set_postfix(dict(N_Depot=len(solution), N_WP=len(wp)))
        p_bar.update()
    return solution

def multi_knapsack_min_packing_covering_exact(station_keys, waypoints, d_matrix, ub_service_time,
                                              warm_starter=None):
    print(f"Building PuLP model to solve a min covering set")
    problem = pulp.LpProblem(f"MinCoveringWithPulp", pulp.LpMinimize)
    y = [pulp.LpVariable(f"y_{i}", 0, 1, pulp.LpInteger) for i in trange(len(station_keys), desc='y vars')]
    x = {i: {j: pulp.LpVariable(f"x_{i}_{j}", 0, 1, pulp.LpInteger) for j in range(len(waypoints))
             # if d_matrix[i, j] <= ub_service_time
             } for i in
         trange(len(station_keys), desc='x vars')}
    # e = {j: pulp.LpVariable(f"e_{j}", 0, 0) for j, wp in tqdm(enumerate(waypoints))}
    problem += pulp.lpSum(y[i] for i in trange(len(station_keys), desc='ObjFn')), "ObjFn"
    # M = len(waypoints)
    for j in trange(len(waypoints), desc='Visit Constraint'):
        problem += pulp.lpSum(x[i][j] for i in range(len(station_keys))) == 1, f"c_wp_{j}"
        # [x[i][j].setInitalValue(0) for i in range(len(station_keys))]
    for i in trange(len(station_keys), desc='Limit Constraint'):
        # for j, wp in enumerate(waypoints):
        #     problem += x[i][j] * d_matrix[i, j] <= ub_service_time
        # problem += pulp.lpSum(x[i][j] for j in range(len(waypoints))) - M * y[i] <= 0
        # problem += pulp.lpSum(x[i][j] * d_matrix[i, j] for j in range(len(waypoints))) <= ub_service_time
        problem += pulp.lpSum(x[i][j] * d_matrix[i, j] for j in range(len(waypoints))) - ub_service_time * y[
            i] <= 0, f"c_dp_{i}"
    print(f"trying to solve...")
    # problem.solve(pulp.PULP_CBC_CMD(msg=1))
    if warm_starter:
        print("warm starting...")
        for y_id, y_var in enumerate(y):
            if y_id in warm_starter.keys():
                y[y_id].setInitialValue(1)
                for _wp in warm_starter[y_id]:
                    x[y_id][_wp].setInitialValue(1)
            else:
                y[y_id].setInitialValue(0)
    problem.solve(
        # pulp.GUROBI_CMD(msg=1, warmStart=1, options=[('TimeLimit', 180), ('MIPGap', 0.33), ("MIPFocus", 3)])
        pulp.GUROBI_CMD(msg=1, warmStart=1, options=[("MIPFocus", 2)])
    )
    # print()
    solution_keys = {station_keys[idx] for idx, var in enumerate(y) if var.varValue > 0.9}
    return solution_keys

if __name__ == '__main__':
    #exact vs other tests
    pars = parfile_reader("./par_files/par1.json")
    fire_stations = read_fire_stations_file(pars['fire_stations'], pars['crs'])
    waypoints = read_waypoint_file(pars['waypoints_file_for_heuristic'])
    print("Heuristic")
    solution, stations= multi_knapsack_min_packing_covering_solver(waypoints, fire_stations, 5, pars, exact=False)
    print (len(solution))
    print("Exact")
    solution, stations = multi_knapsack_min_packing_covering_solver(waypoints, fire_stations, 5, pars, exact=True)
    print(len(solution))


#!/usr/bin/env python2.7
# coding=utf-8

# standard libraries
from __future__ import print_function
import os
import math
import time
# third-party libraries
import numpy as np
# local packages
import procfunc

# set root path of the detector
detector_root = '..'

if __name__ == "__main__":
    DAC = os.path.join(detector_root, 'data')
    [imgDir, resultDir, timeDir, xmlDir, myXmlDir, allTimeFile] = procfunc.setupDir(DAC)
    # load all the images paths
    [allImageName, imageNum] = procfunc.getImageNames(imgDir)
    img_path_lst = [os.path.join(imgDir, imgName) for imgName in allImageName]

    ##############################################
    ############### detection part ###############
    ##############################################
    # initialize
    cfg_path = os.path.join(detector_root, 'cfg', 'valid.cfg')
    model_path = os.path.join(detector_root, 'model', 'yolo_tiny_dacsdc_best.weights')
    res_bboxes = np.zeros((imageNum, 4), dtype=int)

    # inference
    time_start = time.time()
    net = procfunc.loadModel(cfg_path, model_path)  # load trained model
    procfunc.detectionAndTrackingInParallel(net, img_path_lst, imageNum, res_bboxes)
    #procfunc.detectionAndTrackingSerially(net, img_path_lst, imageNum, res_bboxes)
    time_end = time.time()
    resultRunTime = time_end - time_start

    # write results (write time to allTimeFile and detection results to xml)
    procfunc.storeResultsToXML(res_bboxes, allImageName, myXmlDir)
    procfunc.write(imageNum, resultRunTime, allTimeFile)


# -*- coding: utf-8 -*-
from __future__ import unicode_literals

from django.db import migrations, models
import django.core.validators
import taggit.managers

class Migration(migrations.Migration):

    dependencies = [
        ('events', '0018_auto_20161004_2009'),
    ]

    operations = [
        migrations.AddField(
            model_name='event',
            name='is_published',
            field=models.BooleanField(default=True, verbose_name='szkic'),
        ),
        migrations.AlterField(
            model_name='event',
            name='is_featured',
            field=models.BooleanField(default=False, verbose_name='wyr\xf3\u017cniony'),
        ),
        migrations.AlterField(
            model_name='event',
            name='short_title',
            field=models.CharField(default=b'', max_length=255, verbose_name='tytu\u0142 promocyjny wydarzenia', blank=True),
        ),
        migrations.AlterField(
            model_name='event',
            name='tags',
            field=taggit.managers.TaggableManager(to='taggit.Tag', through='taggit.TaggedItem', blank=True, help_text='A comma-separated list of tags.', verbose_name='Tags'),
        ),
        migrations.AlterField(
            model_name='event',
            name='title',
            field=models.CharField(max_length=255, verbose_name='tytu\u0142 wydarzenia'),
        ),
        migrations.AlterField(
            model_name='event',
            name='title_fb_no',
            field=models.CharField(default=b'', max_length=255, null=True, verbose_name='tytu\u0142 na NIE obiektu FB', blank=True),
        ),
        migrations.AlterField(
            model_name='event',
            name='title_fb_yes',
            field=models.CharField(default=b'', max_length=255, null=True, verbose_name='tytu\u0142 na TAK obiektu FB', blank=True),
        ),
        migrations.AlterField(
            model_name='event',
            name='twitter_tag',
            field=models.CharField(default=b'', validators=[django.core.validators.RegexValidator(regex=b'^([^\\s]+)$', message='Tag twittera nie mo\u017ce zawiera\u0107 spacji', code=b'invalid_twitter_tag')], max_length=32, blank=True, null=True, verbose_name='tag twittera'),
        ),
    ]


import requests
import hashlib
import datetime
import json
import logging
from django.conf import settings

logger = logging.getLogger(__name__)

public_key = settings.MARVEL_PUBLIC_KEY
private_key = settings.MARVEL_PRIVATE_KEY

def get_hash(timestamp):
    hash = hashlib.md5()
    hash.update(str(timestamp).encode('utf-8'))
    hash.update(private_key.encode('utf-8'))
    hash.update(public_key.encode('utf-8'))
    return hash.hexdigest()

def get_auth_credentials():
    timestamp = datetime.datetime.now().timestamp()

    return {
        "ts": timestamp,
        "hash": get_hash(timestamp),
        "apikey": public_key,
    }

def make_request(uri, params):

    auth_creds = get_auth_credentials()
    params.update(auth_creds)

    try:
        response = requests.get(uri, params=params)

    except requests.RequestException as ex:

        logger.error(f"Marvel API network error: {ex} uri {uri} params: {params}")
        return True, "Проблемы с соединением, повторите позже"

    if response.status_code == 200:
        try:
            data = response.json()

            logger.info(f"Marvel API: uri: {uri} params: {params} json: {data}")

            return False, data

        except json.JSONDecodeError as ex:
            logger.error(f"Marvel API json decode error: {ex} uri: {uri} params: {params} body: {response.body}")

            return True, "Ошибка сервера"

    elif response.status_code == 409:
        return True, "Ошибка входных параметров"

    elif response.status_code == 404:
        return True, "Данных не найдено"

    else:
        logger.error(f"Marvel API status_code error: {ex} uri: {uri} params: {params} body: {response.body}")
        return True, "Непредвиденная ошибка, обратитесь к администратору "


# Enter your code here. Read input from STDIN. Print output to STDOUT
import math
import sys
import os

def intStringToList(sl):
    newList = [int(x) for x in sl.split()]
    newList.sort()
    return newList

def getMean(numList):
    return (sum(numList)/len(numList))

def getMedian(numList,listLength):
    if listLength % 2 == 1:
        return numList[int((listLength-1)/2)]
    else:
         return ((numList[int(listLength/2)-1] + numList[int(listLength/2)])/2)

def getMode(numList):
    max = 0
    s = set(numList)
    numDict = {i : 0 for i in s}
    for x in range(len(numList)):
        if numList[x] in numDict:
            numDict[numList[x]] = numDict.get(numList[x]) + 1
        if numDict[numList[x]] > max:
            max = numDict[numList[x]]
    modes = []
    for y in numDict:
        if numDict[y] == max:
            modes.append(y)

    return min(modes)

# SD = (((x1-m)**2+(x2-m)**2+(x3-m)**2+(x4-m)**2+...(xN-m)**2))/N)**0.5
def getStandDiv(numList,listLength,listMean):
    sum = 0
    for x in range(len(numList)):
        sum += ((numList[x]-listMean)**2)

    return round(((sum/listLength)**0.5),1)

def printUpperLowerCI(standardDiv,mean,length):
    upper = str(round((mean + 1.96*(standardDiv/math.sqrt(length))),1))
    lower = str(round((mean - 1.96*(standardDiv/math.sqrt(length))),1))
    print(lower + " " + upper)

if __name__ == "__main__":
    length = int(input())
    inputString = input()
    numbers = intStringToList(inputString)

    mean = getMean(numbers)
    print(mean)
    print(getMedian(numbers,length))
    print(getMode(numbers))
    stanDiv = getStandDiv(numbers,length,mean)
    print(stanDiv)
    printUpperLowerCI(stanDiv,mean,length)


# -*- coding: utf-8 -*-
from threading import Thread  # 多线程库
from queue import Queue  # 队列库
import requests
import re  # 正则库
from openpyxl import Workbook  # 操作xlsx文件的库
import time
import cchardet  # 用来检测网页编码的库

class UrlCheck(Thread):
    def __init__(self, url_queue, list_result):
        super().__init__()
        self.url_queue = url_queue  # 要探测的url队列
        self.list_result = list_result  # 结果数组
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (compatible; Baiduspider-render/2.0; +http://www.baidu.com/search/spider.html)'  # 百度爬虫的ua，可能绕过一些反爬
        }

    def run(self):
        while True:
            try:
                url = self.url_queue.get()
                if 'http' not in url:
                    url = "http://" + url
                req = self.download(url)
                if req:
                    title = re.search(r'<title>(.*?)</title>', req.text, re.I | re.S)
                    if title:
                        title = title.group(1)
                    else:
                        title = ""
                    list_result2 = [url, req.status_code, title]
                    self.list_result.append(list_result2)
                    print(url, '状态码：', req.status_code, '标题：', title)
            finally:
                self.url_queue.task_done()

    def download(self, url, retries=3):
        requests.packages.urllib3.disable_warnings()
        try:
            req = requests.get(url, headers=self.headers, timeout=3, verify=False)
        except requests.Timeout:
            req = None
            if retries > 0:
                return self.download(url, retries - 1)
        except requests.RequestException as err:
            req = None
            print(f"fetching {url} error: {err}")
        else:
            encoding = cchardet.detect(req.content)['encoding']
            req.encoding = encoding
        return req

if __name__ == '__main__':
    u_queue = Queue()
    checker = [k.strip() for k in open('urls.txt', encoding='utf-8')]
    list_result = []
    for url in checker:
        u_queue.put(url)
    print("开始探测：")
    for i in range(100):
        bdm = UrlCheck(u_queue, list_result)
        bdm.daemon = True
        bdm.start()

    u_queue.join()

    wb = Workbook()
    ws = wb.active
    ws.append(['url', '状态码', '标题'])
    for list1 in list_result:
        ws.append(list1)
    wb.save(time.strftime("%Y%m%d%H%M%S", time.localtime())+'.xlsx')

    print('done!')


from __future__ import annotations
from sklearn.neural_network import MLPRegressor
from forecastingbackend.MLModels.ModelInterface.MLModelBuilder import MLModelBuilder

class MLPBuilder(MLModelBuilder):
	def __init__(self) -> None:
		self.reset()

	def reset(self) -> None:
		self._model = MLPRegressor()

	@property
	def release_model(self):
		completed_model = self._model
		self.reset()
		return completed_model

	def build_model(self, hyper_param_dict: dict):
		if hyper_param_dict is None:
			self._model = MLPRegressor()
		else:
			self._model = MLPRegressor(
				hidden_layer_sizes=hyper_param_dict["hidden_layer_sizes"],
				activation=hyper_param_dict["activation"],
				learning_rate=hyper_param_dict["learning_rate"],
				solver=hyper_param_dict["solver"]
			)
		return self.release_model


from ultralytics import YOLO
import cv2
# Load a model
model = YOLO('./yolov8x.pt')  # load an official detection model

# Track with the model
results = model(f'bus.jpg')[0]
boxes = results.boxes
names = results.names
img = results.orig_img
if boxes is not None:
    for box in reversed(boxes):
        conf = int(box.conf.squeeze() * 100)
        x1,y1,x2,y2 = (int(i) for i in box.xyxy.squeeze())
        cv2.putText(img, f'{names[int(box.cls)]} {conf}%', (x1,y1-10), cv2.FONT_HERSHEY_SIMPLEX, 0.9, (0,0,255), 2)
        cv2.rectangle(img, (x1,y1), (x2,y2), (0,0,255), 4)
        print(f'{names[int(box.cls)]}  {x1}-{y1}-{x2}-{y2}')
        print('=========================================================')
    cv2.imshow('img', img)
    cv2.waitKey(0)


with open("day-06/input.txt", "r") as file:
    puzzle_input = [int(i) for i in file.readline().strip().split(",")]

def spawn_fish(initial_state, days):
    buckets = [0] * 9

    for fish in initial_state:
        buckets[fish] += 1

    for day in range(days):
        i = day % 9
        n = (i + 7) % 9

        buckets[n] += buckets[i]

    return sum(buckets)

part_1 = lambda inp: spawn_fish(inp, 80)
part_2 = lambda inp: spawn_fish(inp, 256)

print(part_1(puzzle_input))
print(part_2(puzzle_input))


from flask import current_app as app
from flask import request, jsonify
from flask_login import login_user, logout_user, login_required, current_user

from sqlalchemy.exc import IntegrityError
from http import HTTPStatus

from audio_processing.models import User
from audio_processing.app import db, login_manager

@login_manager.user_loader
def load_user(id):
    return User.query.filter_by(id=id).first()

@app.route('/auth/login',  methods=['POST'])
def login():
  google_auth_id = request.form.get('google_auth_id')
  if not google_auth_id:
    return "need valid google account", HTTPStatus.BAD_REQUEST

  google_email = request.form.get('google_email')
  if not google_email:
    return "needs valid email", HTTPStatus.BAD_REQUEST

  user = User.query.filter_by(google_auth_id=google_auth_id, google_email=google_email).first()
  if user:
    print("logging in user:", user)
    login_user(user)
    return jsonify({'message': f"logging in existing user: {user.user_name}"}), HTTPStatus.OK

  user_name = request.form.get('user_name')
  if not user_name:
    return jsonify({'error': 'need non empty user name'}), HTTPStatus.BAD_REQUEST

  # otherwise, create this user
  user = User(
    user_name=user_name,
    google_email=google_email,
    google_auth_id=google_auth_id,
  )
  db.session.add(user)
  try:
    db.session.commit()
  except IntegrityError:
    db.session.rollback()
    return jsonify({'error': 'malicious attempt to create user - user already exists'}), HTTPStatus.BAD_REQUEST

  login_user(user)
  return jsonify({'message': 'created new user'}), HTTPStatus.CREATED

@app.route('/auth/logout',  methods=['POST'])
@login_required
def logout():
  logout_user()
  return jsonify({'message': 'user logged out'}), HTTPStatus.OK


"""******************************** 开始
    author:惊修
    time:$
   ******************************* 结束"""
from django.urls import path
from .views import *

app_name = 'userprofile'

urlpatterns = [
    path('login/', user_login, name='login'),
    path('logout/', user_logout, name='logout'),
    path('register/',user_register, name='register'),
    path('delete/<int:id>', user_delete, name='delete'),
    path('edit/<int:id>', profile_edit, name='profile_eidt'),

]