#@+leo-ver=5-thin
#@+node:ekr.20140907131341.18707: * @file ../plugins/qt_tree.py
"""Leo's Qt tree class."""
#@+<< qt_tree imports >>
#@+node:ekr.20140907131341.18709: ** << qt_tree imports >>
from __future__ import annotations
from collections.abc import Callable
import re
import time
from typing import Any, TYPE_CHECKING
from leo.core.leoQt import isQt6, QtCore, QtGui, QtWidgets
from leo.core.leoQt import EndEditHint, Format, ItemFlag, KeyboardModifier
from leo.core import leoGlobals as g
from leo.core import leoFrame
from leo.core import leoPlugins  # Uses leoPlugins.TryNext.
from leo.plugins import qt_text
#@-<< qt_tree imports >>
#@+<< qt_tree annotations >>
#@+node:ekr.20220417193741.1: ** << qt_tree annotations >>
if TYPE_CHECKING:  # pragma: no cover
    from leo.core.leoCommands import Commands as Cmdr
    from leo.core.leoFrame import LeoQTreeWidget
    from leo.core.leoGui import LeoKeyEvent as Event
    from leo.core.leoNodes import Position, VNode
    from leo.plugins.qt_frame import LeoQtFrame
    from leo.plugins.qt_text import QTextEditWrapper as Wrapper
    Editor = Any
    Icon = Any
    Item = Any
    Widget = Any
#@-<< qt_tree annotations >>
#@+others
#@+node:ekr.20160514120051.1: ** class LeoQtTree
class LeoQtTree(leoFrame.LeoTree):
    """Leo Qt tree class"""
    #@+others
    #@+node:ekr.20110605121601.18404: *3* qtree.Birth
    #@+node:ekr.20110605121601.18405: *4* qtree.__init__
    def __init__(self, c: Cmdr, frame: LeoQtFrame) -> None:  # Frame is a LeoQtFrame.
        """Ctor for the LeoQtTree class."""
        super().__init__(frame)
        self.c = c
        # Widget independent status ivars...
        self.prev_v = None
        self.redrawCount = 0  # Count for debugging.
        self.revertHeadline = None  # Previous headline text for abortEditLabel.
        self.busy = False
        # Associating items with position and vnodes...
        self.items: list[Item] = []
        self.item2positionDict: dict[str, Position] = {}  # Keys are gnxs.
        self.item2vnodeDict: dict[str, VNode] = {}  # Keys are gnxs.
        self.nodeIconsDict: dict[str, list[Icon]] = {}  # keys are gnxs, values are declutter generated icons
        self.position2itemDict: dict[str, Item] = {}  # Keys are gnxs.
        self.vnode2itemsDict: dict[VNode, list[Item]] = {}  # values are lists of items.
        self.editWidgetsDict: dict[Editor, Wrapper] = {}  # keys are native edit widgets, values are wrappers.
        self.reloadSettings()
        # Components...
        self.canvas = self  # An official ivar used by Leo's core.
        self.headlineWrapper: Any = qt_text.QHeadlineWrapper  # This is a class.
        self.treeWidget: LeoQTreeWidget = frame.top.treeWidget
        w = self.treeWidget
        # Declutter data...
        self.declutter_patterns: list[Any] = None  # list of pairs of patterns for decluttering
        self.declutter_data: dict[Any, Any] = {}
        self.loaded_images: dict[str, Icon] = {}

        if 0:  # None of this works.
            #@+<< Drag and drop >>
            #@+node:ekr.20220913074246.1: *5* << Drag and drop >>
            w.setDragEnabled(True)
            w.viewport().setAcceptDrops(True)
            w.showDropIndicator = True
            w.setAcceptDrops(True)
            w.setDragDropMode(w.InternalMove)
            if 1:  # Does not work

                def dropMimeData(self, data: str, action: str, row: str, col: str, parent: str) -> None:
                    g.trace()

                # w.dropMimeData = dropMimeData

                def mimeData(self, indexes: str) -> None:
                    g.trace()
            #@-<< Drag and drop >>

        # Early inits...
        try:
            w.headerItem().setHidden(True)
        except Exception:
            pass
        n = c.config.getInt('icon-height') or 16
        w.setIconSize(QtCore.QSize(160, n))
    #@+node:ekr.20110605121601.17866: *4* qtree.get_name
    def getName(self) -> str:
        """Return the name of this widget: must start with "canvas"."""
        return 'canvas(tree)'
    #@+node:ekr.20110605121601.18406: *4* qtree.initAfterLoad
    def initAfterLoad(self) -> None:
        """Do late-state inits."""
        # Called by Leo's core.
        c = self.c
        # w = c.frame.top
        tw = self.treeWidget
        tw.itemDoubleClicked.connect(self.onItemDoubleClicked)
        tw.itemClicked.connect(self.onItemClicked)
        if self.use_mouse_expand_gestures:
            tw.setMouseTracking(True)
            tw.itemEntered.connect(self.onItemEntered)
        tw.itemSelectionChanged.connect(self.onTreeSelect)
        tw.itemCollapsed.connect(self.onItemCollapsed)
        tw.itemExpanded.connect(self.onItemExpanded)
        tw.customContextMenuRequested.connect(self.onContextMenu)
        # tw.onItemChanged.connect(self.onItemChanged)
        g.app.gui.setFilter(c, tw, self, tag='tree')
        # 2010/01/24: Do not set this here.
        # The read logic sets c.changed to indicate nodes have changed.
        # c.clearChanged()
    #@+node:ekr.20110605121601.17871: *4* qtree.reloadSettings
    def reloadSettings(self) -> None:
        """LeoQtTree."""
        c = self.c
        self.auto_edit = c.config.getBool('single-click-auto-edits-headline', False)
        self.enable_drag_messages = c.config.getBool("enable-drag-messages")
        self.select_all_text_when_editing_headlines = c.config.getBool(
            'select_all_text_when_editing_headlines')
        self.stayInTree = c.config.getBool('stayInTreeAfterSelect')
        self.use_chapters = c.config.getBool('use-chapters')
        self.use_declutter = c.config.getBool('tree-declutter', default=False)
        self.use_mouse_expand_gestures = c.config.getBool('use-mouse-expand-gestures',
                                                           default = False)
    #@+node:ekr.20110605121601.17940: *4* qtree.wrapQLineEdit
    def wrapQLineEdit(self, w: Wrapper) -> Wrapper:
        """A wretched kludge for MacOs k.masterMenuHandler."""
        c = self.c
        if isinstance(w, QtWidgets.QLineEdit):
            wrapper = self.edit_widget(c.p)
        else:
            wrapper = w
        return wrapper
    #@+node:ekr.20110605121601.17868: *3* qtree.Debugging & tracing
    def error(self, s: str) -> None:
        if not g.unitTesting:
            g.trace('LeoQtTree Error: ', s, g.callers())

    def traceItem(self, item: Item) -> str:
        if item:
            # A QTreeWidgetItem.
            return f"item {id(item)}: {self.getItemText(item)}"
        return '<no item>'
    #@+node:ekr.20110605121601.17872: *3* qtree.Drawing
    #@+node:ekr.20110605121601.18408: *4* qtree.clear
    def clear(self) -> None:
        """Clear all widgets in the tree."""
        w = self.treeWidget
        w.clear()
    #@+node:ekr.20110605121601.17873: *4* qtree.full_redraw & helpers
    def full_redraw(self, p: Position=None) -> Position:
        """
        Redraw all visible nodes of the tree.
        Preserve the vertical scrolling unless scroll is True.
        """
        c = self.c
        if g.app.disable_redraw:
            return None
        if self.busy:
            return None
        # Cancel the delayed redraw request.
        c.requestLaterRedraw = False
        if not p:
            p = c.currentPosition()
        elif c.hoistStack and p.h.startswith('@chapter') and p.hasChildren():
            # Make sure the current position is visible.
            # Part of fix of bug 875323: Hoist an @chapter node leaves a non-visible node selected.
            p = p.firstChild()
            c.frame.tree.select(p)
            c.setCurrentPosition(p)
        else:
            c.setCurrentPosition(p)
        assert not self.busy, g.callers()
        self.redrawCount += 1
        self.initData()
        try:
            self.busy = True
            self.drawTopTree(p)
        finally:
            self.busy = False
        self.setItemForCurrentPosition()
        return p  # Return the position, which may have changed.

    # Compatibility

    # mypy complains that there is a mismatch with the base redraw method.
    redraw = full_redraw  # type:ignore
    redraw_now = full_redraw  #type:ignore
    #@+node:vitalije.20200329160945.1: *5* tree declutter code
    #@+node:tbrown.20150807090639.1: *6* qtree.declutter_node & helpers
    def declutter_node(self, c: Cmdr, v: VNode, item: Item) -> Icon:
        """declutter_node - change the appearance of a node

        :param commander c: commander containing node
        :param position p: position of node
        :param QWidgetItem item: tree node widget item

        returns composite icon for this node, containing the icon box and perhaps other icons.
        """
        dd = self.declutter_data
        iconVal = v.computeIcon()
        iconName = f'box{iconVal:02d}.png'
        loaded_images = self.loaded_images
        #@+others
        #@+node:vitalije.20200329153544.1: *7* sorted_icons
        def sorted_icons(v: VNode) -> list[str]:
            """
            Returns a list of icon filenames for this node.
            The list is sorted to owner the 'where' key of image dicts.
            """
            icons = c.editCommands.getIconList(v)
            a = [x['file'] for x in icons if x['where'] == 'beforeIcon']
            a.append(iconName)
            a.extend(x['file'] for x in icons if x['where'] == 'beforeHeadline')
            return a
        #@+node:ekr.20171122064635.1: *7* declutter_replace
        def declutter_replace(arg: str, cmd: Callable) -> tuple[Callable, str]:
            """
            Executes cmd if cmd is any replace command and returns
            pair (commander, s), where 'commander' corresponds
            to the executed replacement operation, 's' is the substituted string.
            If cmd is not a replacement command returns (None, None)
            """
            # pylint: disable=undefined-loop-variable

            replacement, s = None, None

            if cmd == 'REPLACE':
                try:
                    s = pattern.sub(arg, text)
                except re.error as e:
                    g.log(
                        f'Error in declutter REPLACE "{e!s}"\n'
                        f'  RULE:{pattern.pattern!r}\n'
                        f'  REPLACE:{arg!r}\n  HEADLINE:{text!r}', color='error')
            elif cmd == 'REPLACE-HEAD':
                s = text[: m.start()].rstrip()
            elif cmd == 'REPLACE-TAIL':
                s = text[m.end() :].lstrip()
            elif cmd == 'REPLACE-REST':
                s = (text[:m.start()] + text[m.end() :]).strip()

            # 's' is string when 'cmd' is recognized
            # and is None otherwise
            if isinstance(s, str):
                # Save the operation

                def replacement(item, s):
                    return item.setText(0, s)

                # ... and apply it
                replacement(item, s)

            return replacement, s
        #@+node:ekr.20171122055719.1: *7* declutter_style
        def declutter_style(arg: str, cmd: Callable) -> tuple[Callable, str]:
            """
            Handles style options and returns pair '(commander, param)',
            where 'commander' is the applied style-modifying operation,
            param - the saved argument of that operation.
            Returns (None, param) if 'cmd' is not a style option.
            """
            # pylint: disable=function-redefined
            param = c.styleSheetManager.expand_css_constants(arg).split()[0]
            modifier: Callable = None
            if cmd == 'ICON':
                def modifier(item: Item, param: str) -> None:
                    # Does not fit well this function. And we cannot
                    # wrap list 'new_icons' in a saved argument as
                    # the list is recreated before each call.
                    new_icons.append(param)
            elif cmd == 'DOCICON':
                param = g.os_path_join(g.os_path_dirname(c.fileName()), param)
                def modifier(item: Item, param: str) -> None:
                    # As above, but for document relative icons
                    new_icons.append(param)
            elif cmd == 'BG':
                def modifier(item: Item, param: str) -> None:
                    item.setBackground(0, QtGui.QBrush(QtGui.QColor(param)))
            elif cmd == 'FG':
                def modifier(item: Item, param: str) -> None:
                    item.setForeground(0, QtGui.QBrush(QtGui.QColor(param)))
            elif cmd == 'FONT':
                def modifier(item: Item, param: str) -> None:
                    item.setFont(0, QtGui.QFont(param))
            elif cmd == 'ITALIC':
                def modifier(item: Item, param: str) -> None:
                    font = item.font(0)
                    font.setItalic(bool(int(param)))
                    item.setFont(0, font)
            elif cmd == 'WEIGHT':
                def modifier(item: Item, param: str) -> None:
                    arg = getattr(QtGui.QFont, param, 75)
                    font = item.font(0)
                    font.setWeight(arg)
                    item.setFont(0, font)
            elif cmd == 'PX':
                def modifier(item: Item, param: str) -> None:
                    font = item.font(0)
                    font.setPixelSize(int(param))
                    item.setFont(0, font)
            elif cmd == 'PT':
                def modifier(item: Item, param: str) -> None:
                    font = item.font(0)
                    font.setPointSize(int(param))
                    item.setFont(0, font)
            # Apply the style update
            if modifier:
                modifier(item, param)
            return modifier, param
        #@+node:vitalije.20200327163522.1: *7* apply_declutter_rules
        def apply_declutter_rules(cmds: list[tuple[Callable, str]]) -> list[Any]:
            """
            Applies all commands for the matched rule. Returns the list
            of the applied operations paired with their single parameter.
            """
            modifiers = []
            for cmd, arg in cmds:
                modifier, param = declutter_replace(arg, cmd)
                if not modifier:
                    modifier, param = declutter_style(arg, cmd)
                if modifier:
                    modifiers.append((modifier, param))
            return modifiers
        #@+node:vitalije.20200329162015.1: *7* preload_images
        def preload_images() -> None:
            for f in new_icons:
                if f not in loaded_images:
                    loaded_images[f] = g.app.gui.getImageImage(f)
        #@-others
        if (v.h, iconVal) in dd:
            # Apply saved adjustments to the text and to the _style_ of the node.
            new_icons, modifiers_and_args = dd[(v.h, iconVal)]
            for modifier, arg in modifiers_and_args:
                modifier(item, arg)
            new_icons = sorted_icons(v) + new_icons
        else:
            text = v.h
            new_icons = []
            modifiers_and_args = []
            for pattern, cmds in self.get_declutter_patterns():
                m = pattern.match(text) or pattern.search(text)
                if m:
                    modifiers_and_args.extend(apply_declutter_rules(cmds))
            # Save the lists of the icons and the adjusting operations.
            dd[(v.h, iconVal)] = new_icons, modifiers_and_args
            new_icons = sorted_icons(v) + new_icons
            preload_images()
        self.nodeIconsDict[v.gnx] = new_icons
        h = ':'.join(new_icons)
        icon = g.app.gui.iconimages.get(h)
        if not icon:
            preload_images()
            images = [loaded_images.get(x) for x in new_icons]
            icon = self.make_composite_icon(images)
            g.app.gui.iconimages[h] = icon
        # There is always at least a box icon.
        return icon
    #@+node:vitalije.20200327162532.1: *6* qtree.get_declutter_patterns
    def get_declutter_patterns(self) -> list[Any]:
        "Initializes self.declutter_patterns from configuration and returns it"
        if self.declutter_patterns is not None:
            return self.declutter_patterns
        c = self.c
        patterns: list[Any] = []
        warned = False
        lines = c.config.getData("tree-declutter-patterns")
        for line in lines:
            try:
                cmd, arg = line.split(None, 1)
            except ValueError:
                # Allow empty arg, and guard against user errors.
                cmd = line.strip()
                arg = ''
            if cmd.startswith('#'):
                pass
            elif cmd == 'RULE':
                try:
                    patterns.append((re.compile(arg), []))
                except re.error:
                    g.log('Invalid declutter pattern: %r' % (arg,), color='error')
                    patterns.append((re.compile('a^'), [])) # create a rule that matches nothing
            else:
                if patterns:
                    patterns[-1][1].append((cmd, arg))
                elif not warned:
                    warned = True
                    g.log('Declutter patterns must start with RULE*',
                        color='error')
        self.declutter_patterns = patterns
        return patterns
    #@+node:ekr.20110605121601.17874: *5* qtree.drawChildren
    def drawChildren(self, p: Position, parent_item: Item) -> None:
        """Draw the children of p if they should be expanded."""
        if not p:
            g.trace('can not happen: no p')
            return
        if p.hasChildren():
            if p.isExpanded():
                self.expandItem(parent_item)
                # Draw the tree recursively.
                for child in p.children():
                    self.drawTree(child, parent_item)
            else:
                # Draw only the hidden *direct* children.
                for child in p.children():
                    self.drawNode(child, parent_item)
                self.contractItem(parent_item)
        else:
            self.contractItem(parent_item)
    #@+node:ekr.20110605121601.17875: *5* qtree.drawNode
    def drawNode(self, p: Position, parent_item: Item) -> Item:
        """Draw the node p."""
        c = self.c
        v = p.v
        # Allocate the QTreeWidgetItem.
        item = self.createTreeItem(p, parent_item)
        # Update the data structures.
        itemHash = self.itemHash(item)
        self.position2itemDict[p.key()] = item
        self.item2positionDict[itemHash] = p.copy()  # was item
        self.item2vnodeDict[itemHash] = v  # was item
        d = self.vnode2itemsDict
        aList = d.get(v, [])
        if item not in aList:
            aList.append(item)
        d[v] = aList
        # Set the headline and maybe the icon.
        self.setItemText(item, p.h)
        # #1310: Add a tool tip.
        item.setToolTip(0, p.h)
        if self.use_declutter:
            icon = self.declutter_node(c, p.v, item)
            if icon:
                item.setIcon(0, icon)
            return item
        # Draw the icon: **Slow**, but allows per-vnode icons.
        icon = self.getCompositeIconImage(p.v)
        if icon:
            item.setIcon(0, icon)
        return item
    #@+node:ekr.20110605121601.17876: *5* qtree.drawTopTree
    def drawTopTree(self, p: Position) -> None:
        """Draw the tree rooted at p."""
        trace = 'drawing' in g.app.debug and not g.unitTesting
        if trace:
            t1 = time.process_time()
        c = self.c
        self.clear()
        # Draw all top-level nodes and their visible descendants.
        if c.hoistStack:
            bunch = c.hoistStack[-1]
            p = bunch.p
            h = p.h
            if len(c.hoistStack) == 1 and h.startswith('@chapter') and p.hasChildren():
                p = p.firstChild()
                while p:
                    self.drawTree(p)
                    p.moveToNext()
            else:
                self.drawTree(p)
        else:
            p = c.rootPosition()
            while p:
                self.drawTree(p)
                p.moveToNext()
        if trace:
            t2 = time.process_time()
            g.trace(f"{t2 - t1:5.2f} sec.", g.callers(5))
    #@+node:ekr.20110605121601.17877: *5* qtree.drawTree
    def drawTree(self, p: Position, parent_item: Item=None) -> None:
        if g.app.gui.isNullGui:
            return
        # Draw the (visible) parent node.
        item = self.drawNode(p, parent_item)
        # Draw all the visible children.
        self.drawChildren(p, parent_item=item)
    #@+node:ekr.20110605121601.17878: *5* qtree.initData
    def initData(self) -> None:
        self.item2positionDict = {}
        self.item2vnodeDict = {}
        self.position2itemDict = {}
        self.vnode2itemsDict = {}
        self.editWidgetsDict = {}
    #@+node:ekr.20110605121601.17880: *4* qtree.redraw_after_contract
    def redraw_after_contract(self, p: Position) -> None:

        if self.busy:
            return
        self.update_expansion(p)
    #@+node:ekr.20110605121601.17881: *4* qtree.redraw_after_expand
    def redraw_after_expand(self, p: Position) -> None:

        if 0:  # Does not work. Newly visible nodes do not show children correctly.
            c = self.c
            c.selectPosition(p)
            self.update_expansion(p)
        else:
            self.full_redraw(p)  # Don't try to shortcut this!
    #@+node:ekr.20110605121601.17882: *4* qtree.redraw_after_head_changed
    def redraw_after_head_changed(self) -> None:
        """Redraw all Qt outline items cloned to c.p."""
        if self.busy:
            return
        c, p = self.c, self.c.p
        if p:
            h = p.h  # 2010/02/09: Fix bug 518823.
            for item in self.vnode2items(p.v):
                if self.isValidItem(item):
                    self.setItemText(item, h)
                    if self.use_declutter:  # #2844.
                        icon = self.declutter_node(c, p, item)
                        item.setIcon(0, icon)  # 0 is the column number.
    #@+node:ekr.20110605121601.17884: *4* qtree.redraw_after_select
    def redraw_after_select(self, p: Position=None) -> None:
        """Redraw the entire tree when an invisible node is selected."""
        if self.busy:
            return
        self.full_redraw(p)
        # c.redraw_after_select calls tree.select indirectly.
        # Do not call it again here.
    #@+node:ekr.20140907201613.18986: *4* qtree.repaint (not used)
    def repaint(self) -> None:
        """Repaint the widget."""
        w = self.treeWidget
        w.repaint()
        w.resizeColumnToContents(0)  # 2009/12/22
    #@+node:ekr.20180817043619.1: *4* qtree.update_expansion
    def update_expansion(self, p: Position) -> None:
        """Update expansion bits for p, including all clones."""
        c = self.c
        w = self.treeWidget
        expand = c.shouldBeExpanded(p)
        if 'drawing' in g.app.debug:
            g.trace('expand' if expand else 'contract')
        item = self.position2itemDict.get(p.key())
        if p:
            try:
                # These generate events, which would trigger a full redraw.
                self.busy = True
                if expand:
                    w.expandItem(item)
                else:
                    w.collapseItem(item)
            finally:
                self.busy = False
            w.repaint()
        else:
            g.trace('NO P')
            c.redraw()
    #@+node:ekr.20110605121601.17885: *3* qtree.Event handlers
    #@+node:ekr.20110605121601.17887: *4*  qtree.Click Box
    #@+node:ekr.20110605121601.17888: *5* qtree.onClickBoxClick
    def onClickBoxClick(self, event: Event, p: Position=None) -> None:
        if self.busy:
            return
        c = self.c
        g.doHook("boxclick1", c=c, p=p, event=event)
        g.doHook("boxclick2", c=c, p=p, event=event)
        c.outerUpdate()
    #@+node:ekr.20110605121601.17889: *5* qtree.onClickBoxRightClick
    def onClickBoxRightClick(self, event: Event, p: Position=None) -> None:
        if self.busy:
            return
        c = self.c
        g.doHook("boxrclick1", c=c, p=p, event=event)
        g.doHook("boxrclick2", c=c, p=p, event=event)
        c.outerUpdate()
    #@+node:ekr.20110605121601.17890: *5* qtree.onPlusBoxRightClick
    def onPlusBoxRightClick(self, event: Event, p: Position=None) -> None:
        if self.busy:
            return
        c = self.c
        g.doHook('rclick-popup', c=c, p=p, event=event, context_menu='plusbox')
        c.outerUpdate()
    #@+node:ekr.20110605121601.17891: *4*  qtree.Icon Box
    # For Qt, there seems to be no way to trigger these events.
    #@+node:ekr.20110605121601.17892: *5* qtree.onIconBoxClick
    def onIconBoxClick(self, event: Event, p: Position=None) -> None:
        if self.busy:
            return
        c = self.c
        g.doHook("iconclick1", c=c, p=p, event=event)
        g.doHook("iconclick2", c=c, p=p, event=event)
        c.outerUpdate()
    #@+node:ekr.20110605121601.17893: *5* qtree.onIconBoxRightClick
    def onIconBoxRightClick(self, event: Event, p: Position=None) -> None:
        """Handle a right click in any outline widget."""
        if self.busy:
            return
        c = self.c
        g.doHook("iconrclick1", c=c, p=p, event=event)
        g.doHook("iconrclick2", c=c, p=p, event=event)
        c.outerUpdate()
    #@+node:ekr.20110605121601.17894: *5* qtree.onIconBoxDoubleClick
    def onIconBoxDoubleClick(self, event: Event, p: Position=None) -> None:
        if self.busy:
            return
        c = self.c
        if not p:
            p = c.p
        if not g.doHook("icondclick1", c=c, p=p, event=event):
            self.endEditLabel()
            self.OnIconDoubleClick(p)  # Call the method in the base class.
        g.doHook("icondclick2", c=c, p=p, event=event)
        c.outerUpdate()
    #@+node:ekr.20110605121601.18437: *4* qtree.onContextMenu
    def onContextMenu(self, point: Any) -> None:
        """LeoQtTree: Callback for customContextMenuRequested events."""
        # #1286.
        c, w = self.c, self.treeWidget
        g.app.gui.onContextMenu(c, w, point)
    #@+node:ekr.20110605121601.17896: *4* qtree.onItemClicked
    def onItemClicked(self, item: Item, col: int) -> None:  # Col not used.
        """Handle a click in a BaseNativeTree widget item."""
        # This is called after an item is selected.
        if self.busy:
            return
        c = self.c
        try:
            self.busy = True
            p = self.item2position(item)
            if p:
                auto_edit = self.prev_v == p.v  # #1049.
                self.prev_v = p.v
                event = None
                #
                # Careful. We may have switched gui during unit testing.
                if hasattr(g.app.gui, 'qtApp'):
                    mods = g.app.gui.qtApp.keyboardModifiers()
                    isCtrl = bool(mods & KeyboardModifier.ControlModifier)
                    # We could also add support for QtConst.ShiftModifier, QtConst.AltModifier
                    # & QtConst.MetaModifier.
                    if isCtrl:
                        if g.doHook("iconctrlclick1", c=c, p=p, event=event) is None:
                            c.frame.tree.OnIconCtrlClick(p)  # Call the base class method.
                        g.doHook("iconctrlclick2", c=c, p=p, event=event)
                    else:
                        # 2014/02/21: generate headclick1/2 instead of iconclick1/2
                        g.doHook("headclick1", c=c, p=p, event=event)
                        g.doHook("headclick2", c=c, p=p, event=event)
            else:
                auto_edit = None
                g.trace('*** no p')
            # 2011/05/27: click here is like ctrl-g.
            c.k.keyboardQuit(setFocus=False)
            c.treeWantsFocus()  # 2011/05/08: Focus must stay in the tree!
            c.outerUpdate()
            # 2011/06/01: A second *single* click on a selected node
            # enters editing state.
            if auto_edit and self.auto_edit:
                e, wrapper = self.createTreeEditorForItem(item)
        finally:
            self.busy = False
    #@+node:ekr.20110605121601.17895: *4* qtree.onItemCollapsed
    def onItemCollapsed(self, item: Item) -> None:

        if self.busy:
            return
        c = self.c
        p = self.item2position(item)
        if not p:
            self.error('no p')
            return
        # Do **not** set lockouts here.
        # Only methods that actually generate events should set lockouts.
        if p.isExpanded():
            p.contract()
            c.redraw_after_contract(p)
        self.select(p)
        c.outerUpdate()
    #@+node:ekr.20110605121601.17897: *4* qtree.onItemDoubleClicked
    def onItemDoubleClicked(self, item: Item, col: Any) -> None:  # col not used.
        """Handle a double click in a BaseNativeTree widget item."""
        if self.busy:  # Required.
            return
        c = self.c
        try:
            self.busy = True
            e, wrapper = self.createTreeEditorForItem(item)
            if not e:
                g.trace('*** no e')
            p = self.item2position(item)
        # 2011/07/28: End the lockout here, not at the end.
        finally:
            self.busy = False
        if not p:
            self.error('no p')
            return
        # 2014/02/21: generate headdlick1/2 instead of icondclick1/2.
        if g.doHook("headdclick1", c=c, p=p, event=None) is None:
            c.frame.tree.OnIconDoubleClick(p)  # Call the base class method.
        g.doHook("headclick2", c=c, p=p, event=None)
        c.outerUpdate()
    #@+node:ekr.20110605121601.17898: *4* qtree.onItemExpanded
    def onItemExpanded(self, item: Item) -> None:
        """Handle and tree-expansion event."""
        if self.busy:  # Required
            return
        c = self.c
        p = self.item2position(item)
        if not p:
            self.error('no p')
            return
        # Do **not** set lockouts here.
        # Only methods that actually generate events should set lockouts.
        if not p.isExpanded():
            p.expand()
            c.redraw_after_expand(p)
        self.select(p)
        c.outerUpdate()
    #@+node:ekr.20110605121601.17899: *4* qtree.onTreeSelect
    def onTreeSelect(self) -> None:
        """Select the proper position when a tree node is selected."""
        if self.busy:  # Required
            return
        c = self.c
        item = self.getCurrentItem()
        p = self.item2position(item)
        if not p:
            self.error(f"no p for item: {item}")
            return
        # Do **not** set lockouts here.
        # Only methods that actually generate events should set lockouts.
        self.select(p)  # This is a call to LeoTree.select(!!)
        c.outerUpdate()
    #@+node:ekr.20110605121601.17944: *3* qtree.Focus
    def getFocus(self) -> Any:
        return g.app.gui.get_focus(self.c)  # Bug fix: 2009/6/30

    findFocus = getFocus

    def setFocus(self) -> None:
        g.app.gui.set_focus(self.c, self.treeWidget)
    #@+node:tom.20230324155453.1: *3* qtree.onItemEntered
    def onItemEntered(self, item: Item, col: int):
        """Expand/Contract a node when mouse moves over it.

        <CTRL-hover> -- expand;
        <SHIFT-hover> -- contract.
        """
        if not self.use_mouse_expand_gestures:
            return

        if hasattr(g.app.gui, 'qtApp'):
            mods = g.app.gui.qtApp.keyboardModifiers()
            isCtrl = bool(mods & KeyboardModifier.ControlModifier)
            isShift = bool(mods & KeyboardModifier.ShiftModifier)
            if isCtrl and not isShift:
                self.expandItem(item)
            elif isShift and not isCtrl:
                self.contractItem(item)
    #@+node:ekr.20110605121601.18409: *3* qtree.Icons
    #@+node:ekr.20110605121601.18411: *4* qtree.getIcon & helpers
    def getIcon(self, v: VNode) -> Icon:
        """Return the proper icon for position p."""
        if self.use_declutter:
            items = self.vnode2items(v)
            if items:
                return self.declutter_node(self.c, v, items[0])
        return self.getCompositeIconImage(v)
    #@+node:vitalije.20200329153148.1: *5* qtree.icon_filenames_for_node
    def icon_filenames_for_node(self, v: VNode) -> list[str]:
        """Returns a list of icon filenames for v."""
        nicon = f'box{v.iconVal:02d}.png'
        fnames = self.nodeIconsDict.get(v.gnx)
        if not fnames:
            icons = self.c.editCommands.getIconList(v)
            fnames = [x['file'] for x in icons if x['where'] == 'beforeIcon']
            fnames.append(nicon)
            fnames.extend(x['file'] for x in icons if x['where'] == 'beforeHeadline')
            self.nodeIconsDict[v.gnx] = fnames
        pat = re.compile(r'^box\d\d\.png$')
        loaded_images = self.loaded_images
        for i, f in enumerate(fnames):
            if pat.match(f):
                fnames[i] = nicon
                self.nodeIconsDict[v.gnx] = fnames
                f = nicon
            if f not in loaded_images:
                loaded_images[f] = g.app.gui.getImageImage(f)
        return fnames
    #@+node:vitalije.20200329153154.1: *5* qtree.make_composite_icon
    def make_composite_icon(self, images: list[Any]) -> Icon:
        hsep = self.c.config.getInt('tree-icon-separation') or 0
        images = [x for x in images if x]
        height = max([i.height() for i in images])
        images = [i.scaledToHeight(height) for i in images]
        width = sum([i.width() for i in images]) + hsep * (len(images) - 1)
        pix = QtGui.QImage(width, height, Format.Format_ARGB32_Premultiplied)
        pix.fill(QtGui.QColor(0, 0, 0, 0))
        painter = QtGui.QPainter()
        ok = painter.begin(pix)
        x = 0
        for i in images:
            painter.drawPixmap(x, 0, i)
            x += i.width() + hsep
        if ok:
            painter.end()
        return QtGui.QIcon(QtGui.QPixmap.fromImage(pix))
    #@+node:ekr.20110605121601.18412: *5* qtree.getCompositeIconImage
    def getCompositeIconImage(self, v: VNode) -> Icon:
        """Get the icon at v."""
        v.iconVal = v.computeIcon()
        fnames = self.icon_filenames_for_node(v)
        h = ':'.join(fnames)
        icon = g.app.gui.iconimages.get(h)
        loaded_images = self.loaded_images
        images = list(map(loaded_images.get, fnames))
        if not icon:
            icon = self.make_composite_icon(images)
            g.app.gui.iconimages[h] = icon
        return icon
    #@+node:ekr.20110605121601.17950: *4* qtree.setItemIcon
    def setItemIcon(self, item: Item, icon: str) -> None:

        valid = item and self.isValidItem(item)
        if icon and valid:
            # Important: do not set lockouts here.
            # This will generate changed events,
            # but there is no itemChanged event handler.
            item.setIcon(0, icon)

    #@+node:ekr.20110605121601.18414: *3* qtree.Items
    #@+node:ekr.20110605121601.17943: *4*  qtree.item dict getters
    def itemHash(self, item: Item) -> str:
        return f"{repr(item)} at {str(id(item))}"

    def item2position(self, item: Item) -> Position:
        itemHash = self.itemHash(item)
        p = self.item2positionDict.get(itemHash)  # was item
        return p

    def item2vnode(self, item: Item) -> VNode:
        itemHash = self.itemHash(item)
        return self.item2vnodeDict.get(itemHash)  # was item

    def position2item(self, p: Position) -> Item:
        item = self.position2itemDict.get(p.key())
        return item

    def vnode2items(self, v: VNode) -> list[Item]:
        return self.vnode2itemsDict.get(v, [])

    def isValidItem(self, item: Item) -> bool:
        itemHash = self.itemHash(item)
        return itemHash in self.item2vnodeDict  # was item.
    #@+node:ekr.20110605121601.18415: *4* qtree.childIndexOfItem
    def childIndexOfItem(self, item: Item) -> int:
        parent = item and item.parent()
        if parent:
            n = parent.indexOfChild(item)
        else:
            w = self.treeWidget
            n = w.indexOfTopLevelItem(item)
        return n
    #@+node:ekr.20110605121601.18416: *4* qtree.childItems
    def childItems(self, parent_item: Item) -> list[Item]:
        """
        Return the list of child items of the parent item,
        or the top-level items if parent_item is None.
        """
        if parent_item:
            n = parent_item.childCount()
            items = [parent_item.child(z) for z in range(n)]
        else:
            w = self.treeWidget
            n = w.topLevelItemCount()
            items = [w.topLevelItem(z) for z in range(n)]
        return items
    #@+node:ekr.20110605121601.18418: *4* qtree.connectEditorWidget & callback
    def connectEditorWidget(self, e: Editor, item: Item) -> Wrapper:
        """
        Connect QLineEdit e to QTreeItem item.

        Also callback for when the editor ends.

        New in Leo 6.4: The callback handles all updates w/o calling onHeadChanged.
        """
        c, p, u = self.c, self.c.p, self.c.undoer
        #@+others  # define the callback.
        #@+node:ekr.20201109043641.1: *5* function: editingFinished_callback
        def editingFinished_callback() -> None:
            """Called when Qt emits the editingFinished signal."""
            s = e.text()
            i = s.find('\n')
            # Truncate to one line.
            if i > -1:
                s = s[:i]
            # #1310: update the tooltip.
            if p.h != s:
                # Update p.h and handle undo.
                item.setToolTip(0, s)
                undoData = u.beforeChangeHeadline(p)
                p.v.setHeadString(s)  # Set v.h *after* calling the undoer's before method.
                if not c.changed:
                    c.setChanged()
                # We must recolor the body because
                # the headline may contain directives.
                c.frame.body.recolor(p)
                p.setDirty()
                u.afterChangeHeadline(p, 'Edit Headline', undoData)
            self.redraw_after_head_changed()
            c.outerUpdate()
        #@-others
        if e:
            # Hook up the widget.
            wrapper = self.getWrapper(e, item)
            e.editingFinished.connect(editingFinished_callback)
            return wrapper  # 2011/02/12
        g.trace('can not happen: no e')
        return None
    #@+node:ekr.20110605121601.18419: *4* qtree.contractItem & expandItem
    def contractItem(self, item: Item) -> None:
        self.treeWidget.collapseItem(item)

    def expandItem(self, item: Item) -> None:
        self.treeWidget.expandItem(item)
    #@+node:ekr.20110605121601.18420: *4* qtree.createTreeEditorForItem
    def createTreeEditorForItem(self, item: Item) -> tuple[Editor, Wrapper]:

        c = self.c
        w = self.treeWidget
        w.setCurrentItem(item)  # Must do this first.
        if self.use_declutter:
            item.setText(0, item._real_text)
        w.editItem(item)
        e = w.itemWidget(item, 0)  # e is a QLineEdit
        e.setObjectName('headline')
        wrapper = self.connectEditorWidget(e, item)
        self.sizeTreeEditor(c, e)
        return e, wrapper
    #@+node:ekr.20110605121601.18421: *4* qtree.createTreeItem
    def createTreeItem(self, p: Position, parent_item: Item) -> Item:

        w = self.treeWidget
        itemOrTree = parent_item or w
        item = QtWidgets.QTreeWidgetItem(itemOrTree)
        if isQt6:
            item.setFlags(item.flags() | ItemFlag.ItemIsEditable)
            ChildIndicatorPolicy = QtWidgets.QTreeWidgetItem.ChildIndicatorPolicy
            item.setChildIndicatorPolicy(
                ChildIndicatorPolicy.DontShowIndicatorWhenChildless)  # pylint: disable=no-member
        else:
            item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable | item.DontShowIndicatorWhenChildless)
        try:
            g.visit_tree_item(self.c, p, item)
        except leoPlugins.TryNext:
            pass
        return item
    #@+node:ekr.20110605121601.18423: *4* qtree.getCurrentItem
    def getCurrentItem(self) -> Item:
        w = self.treeWidget
        return w.currentItem()
    #@+node:ekr.20110605121601.18424: *4* qtree.getItemText
    def getItemText(self, item: Item) -> str:
        """Return the text of the item."""
        return item.text(0) if item else '<no item>'
    #@+node:ekr.20110605121601.18425: *4* qtree.getParentItem
    def getParentItem(self, item: Item) -> Item:
        return item and item.parent()
    #@+node:ekr.20110605121601.18426: *4* qtree.getSelectedItems
    def getSelectedItems(self) -> list:
        w = self.treeWidget
        return w.selectedItems()
    #@+node:ekr.20110605121601.18427: *4* qtree.getTreeEditorForItem
    def getTreeEditorForItem(self, item: Item) -> Editor:
        """Return the edit widget if it exists.
        Do *not* create one if it does not exist.
        """
        w = self.treeWidget
        e = w.itemWidget(item, 0)
        return e
    #@+node:ekr.20110605121601.18428: *4* qtree.getWrapper
    def getWrapper(self, e: Editor, item: Item) -> Wrapper:
        """Return headlineWrapper that wraps e (a QLineEdit)."""
        c = self.c
        if e:
            wrapper = self.editWidgetsDict.get(e)
            if wrapper:
                pass
            else:
                if item:
                    # 2011/02/12: item can be None.
                    wrapper = self.headlineWrapper(c, item, name='head', widget=e)
                    self.editWidgetsDict[e] = wrapper
            return wrapper
        g.trace('no e')
        return None
    #@+node:ekr.20110605121601.18429: *4* qtree.nthChildItem
    def nthChildItem(self, n: int, parent_item: Item) -> Item:
        children = self.childItems(parent_item)
        if n < len(children):
            item = children[n]
        else:
            # This is **not* an error.
            # It simply means that we need to redraw the tree.
            item = None
        return item
    #@+node:ekr.20110605121601.18430: *4* qtree.scrollToItem
    def scrollToItem(self, item: Item) -> None:
        """
        Scroll the tree widget so that item is visible.
        Leo's core no longer calls this method.
        """
        w = self.treeWidget
        hPos, vPos = self.getScroll()
        # Fix #265: Erratic scrolling bug.
        # w.PositionAtCenter causes unwanted scrolling.
        w.scrollToItem(item, w.EnsureVisible)
        self.setHScroll(0)  # Necessary
    #@+node:ekr.20110605121601.18431: *4* qtree.setCurrentItemHelper
    def setCurrentItemHelper(self, item: Item) -> None:
        w = self.treeWidget
        w.setCurrentItem(item)
    #@+node:ekr.20110605121601.18432: *4* qtree.setItemText
    def setItemText(self, item: Item, s: str) -> None:
        if item:
            item.setText(0, s)
            if self.use_declutter:
                item._real_text = s
    #@+node:tbrown.20160406221505.1: *4* qtree.sizeTreeEditor
    @staticmethod
    def sizeTreeEditor(c: Cmdr, editor: Editor) -> None:
        """Size a QLineEdit in a tree headline so scrolling occurs"""
        # space available in tree widget
        space = c.frame.tree.treeWidget.size().width()
        # left hand edge of editor within tree widget
        used = editor.geometry().x() + 4  # + 4 for edit cursor
        # limit width to available space
        editor.resize(space - used, editor.size().height())
    #@+node:ekr.20110605121601.18433: *3* qtree.Scroll bars
    #@+node:ekr.20110605121601.18434: *4* qtree.getSCroll
    def getScroll(self) -> tuple[int, int]:
        """Return the hPos,vPos for the tree's scrollbars."""
        w = self.treeWidget
        hScroll = w.horizontalScrollBar()
        vScroll = w.verticalScrollBar()
        hPos = hScroll.sliderPosition()
        vPos = vScroll.sliderPosition()
        return hPos, vPos
    #@+node:btheado.20111110215920.7164: *4* qtree.scrollDelegate
    def scrollDelegate(self, kind: str) -> None:
        """
        Scroll a QTreeWidget up or down or right or left.
        kind is in ('down-line','down-page','up-line','up-page', 'right', 'left')
        """
        c = self.c
        w = self.treeWidget
        if kind in ('left', 'right'):
            hScroll = w.horizontalScrollBar()
            if kind == 'right':
                delta = hScroll.pageStep()
            else:
                delta = -hScroll.pageStep()
            hScroll.setValue(int(hScroll.value() + delta))
        else:
            vScroll = w.verticalScrollBar()
            h = w.size().height()
            lineSpacing = w.fontMetrics().lineSpacing()
            n = h / lineSpacing
            if kind == 'down-half-page':
                delta = n / 2
            elif kind == 'down-line':
                delta = 1
            elif kind == 'down-page':
                delta = n
            elif kind == 'up-half-page':
                delta = -n / 2
            elif kind == 'up-line':
                delta = -1
            elif kind == 'up-page':
                delta = -n
            else:
                delta = 0
                g.trace('bad kind:', kind)
            val = vScroll.value()
            vScroll.setValue(int(val + delta))
        c.treeWantsFocus()
    #@+node:ekr.20110605121601.18435: *4* qtree.setH/VScroll
    def setHScroll(self, hPos: int) -> None:

        w = self.treeWidget
        hScroll = w.horizontalScrollBar()
        hScroll.setValue(hPos)

    def setVScroll(self, vPos: int) -> None:

        w = self.treeWidget
        vScroll = w.verticalScrollBar()
        vScroll.setValue(vPos)
    #@+node:ekr.20110605121601.17905: *3* qtree.Selecting & editing
    #@+node:ekr.20110605121601.17908: *4* qtree.edit_widget
    def edit_widget(self, p: Position) -> Wrapper:
        """Returns the edit widget for position p."""
        item = self.position2item(p)
        if item:
            e = self.getTreeEditorForItem(item)
            if e:
                # Create a wrapper widget for Leo's core.
                w = self.getWrapper(e, item)
                return w
            # This is not an error
            # But warning: calling this method twice might not work!
            return None
        return None
    #@+node:ekr.20110605121601.17909: *4* qtree.editLabel and helper
    def editLabel(self,
        p: Position, selectAll: bool=False, selection: tuple=None,
    ) -> tuple[Editor, Any]:
        """Start editing p's headline."""
        if self.busy:
            return None
        c = self.c
        # Do any scheduled redraw.
        # This won't do anything in the new redraw scheme.
        c.outerUpdate()
        item = self.position2item(p)
        if item:
            if self.use_declutter:
                item.setText(0, item._real_text)
            e, wrapper = self.editLabelHelper(item, selectAll, selection)
        else:
            e, wrapper = None, None
            self.error(f"no item for {p}")
        if e:
            self.sizeTreeEditor(c, e)
            # A nice hack: just set the focus request.
            c.requestedFocusWidget = e
        return e, wrapper
    #@+node:ekr.20110605121601.18422: *5* qtree.editLabelHelper
    def editLabelHelper(self,
        item: Any, selectAll: bool=False, selection: tuple=None,
    ) -> tuple[Item, Any]:
        """Helper for qtree.editLabel."""
        c, vc = self.c, self.c.vimCommands
        w = self.treeWidget
        # Must do this first.
        # This generates a call to onTreeSelect.
        w.setCurrentItem(item)
        w.editItem(item)  # Generates focus-in event that tree doesn't report.
        e = w.itemWidget(item, 0)  # A QLineEdit.
        s = e.text()
        if s == 'newHeadline':
            selectAll = True
        start: int
        n: int
        if selection:
            # pylint: disable=unpacking-non-sequence
            # Fix bug https://groups.google.com/d/msg/leo-editor/RAzVPihqmkI/-tgTQw0-LtwJ
            # Note: negative lengths are allowed.
            i, j, ins = selection
            if ins is None:
                start, n = i, abs(i - j)
                # This case doesn't happen for searches.
            elif ins == j:
                start, n = i, j - i
            else:
                start, n = j, i - j
        elif selectAll:
            start, n, ins = 0, len(s), len(s)
        else:
            start, n, ins = len(s), 0, len(s)
        e.setObjectName('headline')
        e.setSelection(start, n)
        # e.setCursorPosition(ins) # Does not work.
        e.setFocus()
        wrapper = self.connectEditorWidget(e, item)  # Hook up the widget.
        if vc and c.vim_mode:  #  and selectAll
            # For now, *always* enter insert mode.
            if vc.is_text_wrapper(wrapper):
                vc.begin_insert_mode(w=wrapper)
            else:
                g.trace('not a text widget!', wrapper)
        return e, wrapper
    #@+node:ekr.20110605121601.17911: *4* qtree.endEditLabel
    def endEditLabel(self) -> None:
        """
        Override LeoTree.endEditLabel.

        Just end editing of the presently-selected QLineEdit!
        This will trigger the editingFinished_callback defined in createEditorForItem.
        """
        item = self.getCurrentItem()
        if not item:
            return
        e = self.getTreeEditorForItem(item)
        if not e:
            return
        # Trigger the end-editing event.
        w = self.treeWidget
        w.closeEditor(e, EndEditHint.NoHint)
        w.setCurrentItem(item)
    #@+node:ekr.20110605121601.17915: *4* qtree.getSelectedPositions
    def getSelectedPositions(self) -> list[Position]:
        return [self.item2position(z) for z in self.getSelectedItems()]
    #@+node:ekr.20110605121601.17914: *4* qtree.setHeadline
    def setHeadline(self, p: Position, s: str) -> None:
        """Force the actual text of the headline widget to p.h."""
        # This is used by unit tests to force the headline and p into alignment.
        if not p:
            return
        # Don't do this here: the caller should do it.
        # p.setHeadString(s)
        e = self.edit_widget(p)
        if e:
            e.setAllText(s)
        else:
            item = self.position2item(p)
            if item:
                self.setItemText(item, s)
    #@+node:ekr.20110605121601.17913: *4* qtree.setItemForCurrentPosition
    def setItemForCurrentPosition(self) -> None:
        """Select the item for c.p"""
        p = self.c.p
        if self.busy:
            return None
        if not p:
            return None
        item = self.position2item(p)
        if not item:
            # This is not necessarily an error.
            # We often attempt to select an item before redrawing it.
            return None
        item2 = self.getCurrentItem()
        if item == item2:
            return item
        try:
            self.busy = True
            # This generates gui events, so we must use a lockout.
            self.treeWidget.setCurrentItem(item)
        finally:
            self.busy = False
        return item
    #@+node:ekr.20190613080606.1: *4* qtree.unselectItem
    def unselectItem(self, p: Position) -> None:

        item = self.position2item(p)
        if item:
            item.setSelected(False)
    #@-others
#@-others
#@@language python
#@@tabwidth -4
#@@pagewidth 80
#@-leo


#-*- coding: UTF-8 -*-
import sys
from s3 import s3_put_object, s3_connection
from media import audio_request
import os
import sys
import io

access_key = sys.argv[1]
secret_key = sys.argv[2]
region = sys.argv[3]
allText = sys.argv[4]

os.system('chcp 65001')

sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')
s3 = s3_connection(region, access_key, secret_key)

def createAudios(allText):
    texts = [text.strip()+'.' for text in list(filter(None, allText.split('.')))]

    for text in texts:
        print(text)
        data = dict()
        data['text'] = text
        path = 'result'
        audio_id = audio_request(data, path)
        s3_put_object(s3, "jenapark", f"result/{audio_id}.wav",
                      f"audio/{audio_id}.wav")
        os.remove(f"result/{audio_id}.wav")
    return True

createAudios(allText)


import numpy as np

from math import floor

DISTANCE_TO_ROOT = "dist_to_root"

DEPTH = "depth"

HEIGHT = "height"

LADDER = "ladder"

VISITED = "visited"

col_chains = [
        'number_sumchain', 'mean_sumchain', 'min_sumchain', '1st_decile_sumchain', '2nd_decile_sumchain',
        '3rd_decile_sumchain', '4th_decile_sumchain', 'median_sumchain', '6th_decile_sumchain', '7th_decile_sumchain',
        '8th_decile_sumchain', '9th_decile_sumchain', 'max_sumchain', 'var_sumchain'
    ]

def add_depth_and_get_max(tre):
    """
    adds depth to each node. Adapted to polytomial (non binary) trees.
    :param tre: ete3.Tree, the tree to which depth should be added
    :return: modifies the original tree + maximum depth
    """
    maxdepth = 0
    for node in tre.traverse('levelorder'):
        if not node.is_root():
            # initiation at children of root
            if node.up.is_root():
                node.add_feature("depth", 1)
            # then, add 1 depth at each level
            else:
                node.add_feature("depth", getattr(node.up, "depth", False) + 1)
                if getattr(node, "depth", False) > maxdepth:
                    maxdepth = getattr(node, "depth", False)
    return maxdepth

def add_ladder(tr):
    """
    adds ladder score to each node.
    :param tr: ete3.Tree, the tree to which ladder score should be added
    :return: modifies the original tree
    """
    for node in tr.traverse('levelorder'):
        if not node.is_root():
            if node.up.is_root():
                if not node.is_leaf():
                    if node.children[0].is_leaf() or node.children[1].is_leaf():
                        node.add_feature("ladder", 0)
                    else:
                        node.add_feature("ladder", -1)
                else:
                    node.add_feature("ladder", -1)
            else:
                if not node.is_leaf():
                    if node.children[0].is_leaf() and node.children[1].is_leaf():
                        node.add_feature("ladder", 0)
                    elif node.children[0].is_leaf() or node.children[1].is_leaf():
                        node.add_feature("ladder", getattr(node.up, "ladder", False) + 1)
                    else:
                        node.add_feature("ladder", 0)
                else:
                    node.add_feature("ladder", -1)
        else:
            node.add_feature("ladder", -1)
    return None

def tree_height(tre):
    """
    Returns the maximum and minimum height of leaves
    :param tre: ete3.Tree, tree on which these metrics are computed
    :return: set of floats, max and min leaf height
    """
    root_to_tips = [leaf.dist_to_root for leaf in tre]
    max_h = max(root_to_tips)
    min_h = min(root_to_tips)
    return [max_h, min_h]

def branches(tre):
    """
    Returns branch length metrics (all branches taken into account and external only)
    :param tre: ete3.Tree, tree on which these metrics are computed
    :return: set of floats, metrics on all branches
    """
    # branch lengths
    dist_all = [node.dist for node in tre.traverse("levelorder")]
    # external branch lengths
    dist_ext = [leaf.dist for leaf in tre]

    all_bl_mean = np.mean(dist_all)
    all_bl_median = np.median(dist_all)
    all_bl_var = np.nanvar(dist_all)

    ext_bl_mean = np.mean(dist_ext)
    ext_bl_median = np.median(dist_ext)
    ext_bl_var = np.nanvar(dist_ext)

    return [all_bl_mean, all_bl_median, all_bl_var, ext_bl_mean, ext_bl_median, ext_bl_var]

def piecewise_branches(tre, all_max, e_bl_mean, e_bl_median, e_bl_var):
    """
    Returns piecewise branch length metrics
    :param tre: ete3.Tree, tree on which these metrics are computed
    :param all_max: float, tree height
    :param e_bl_mean: float, mean length of external branches
    :param e_bl_median: float, median length of external branches
    :param e_bl_var: float, variance of length of external branches
    :return: list of 18 floats, summary statistics on piecewise branch length
    """
    dist_all_1 = []
    dist_all_2 = []
    dist_all_3 = []

    for node in tre.traverse("levelorder"):
        if node.dist_to_root < all_max / 3 and not node.is_leaf():
            dist_all_1.append(node.dist)
        elif node.dist_to_root < 2 * all_max / 3 and not node.is_leaf():
            dist_all_2.append(node.dist)
        elif node.dist_to_root > 2 * all_max / 3 and not node.is_leaf():
            dist_all_3.append(node.dist)

    if len(dist_all_1) > 0:
        i_bl_mean_1 = np.mean(dist_all_1)
        i_bl_median_1 = np.median(dist_all_1)
        i_bl_var_1 = np.nanvar(dist_all_1)

        ie_bl_mean_1 = np.mean(dist_all_1) / e_bl_mean
        ie_bl_median_1 = np.median(dist_all_1) / e_bl_median
        ie_bl_var_1 = np.nanvar(dist_all_1) / e_bl_var

    else:
        i_bl_mean_1 = 0
        i_bl_median_1 = 0
        i_bl_var_1 = 0

        ie_bl_mean_1 = 0
        ie_bl_median_1 = 0
        ie_bl_var_1 = 0

    if len(dist_all_2) > 0:
        i_bl_mean_2 = np.mean(dist_all_2)
        i_bl_median_2 = np.median(dist_all_2)
        i_bl_var_2 = np.nanvar(dist_all_2)

        ie_bl_mean_2 = np.mean(dist_all_2) / e_bl_mean
        ie_bl_median_2 = np.median(dist_all_2) / e_bl_median
        ie_bl_var_2 = np.nanvar(dist_all_2) / e_bl_var
    else:
        i_bl_mean_2 = 0
        i_bl_median_2 = 0
        i_bl_var_2 = 0

        ie_bl_mean_2 = 0
        ie_bl_median_2 = 0
        ie_bl_var_2 = 0

    if len(dist_all_3) > 0:
        i_bl_mean_3 = np.mean(dist_all_3)
        i_bl_median_3 = np.median(dist_all_3)
        i_bl_var_3 = np.nanvar(dist_all_3)

        ie_bl_mean_3 = np.mean(dist_all_3) / e_bl_mean
        ie_bl_median_3 = np.median(dist_all_3) / e_bl_median
        ie_bl_var_3 = np.nanvar(dist_all_3) / e_bl_var

    else:
        i_bl_mean_3 = 0
        i_bl_median_3 = 0
        i_bl_var_3 = 0

        ie_bl_mean_3 = 0
        ie_bl_median_3 = 0
        ie_bl_var_3 = 0

    return [i_bl_mean_1, i_bl_median_1, i_bl_var_1, ie_bl_mean_1, ie_bl_median_1, ie_bl_var_1, i_bl_mean_2, \
           i_bl_median_2, i_bl_var_2, ie_bl_mean_2, ie_bl_median_2, ie_bl_var_2, i_bl_mean_3, i_bl_median_3, \
           i_bl_var_3, ie_bl_mean_3, ie_bl_median_3, ie_bl_var_3]

def colless(tre):
    """
    Returns colless metric of given tree
    :param tre: ete3.Tree, tree on which these metrics are computed
    :return: float, colless metric
    """
    colless_score = 0
    for node in tre.traverse("levelorder"):
        if not node.is_leaf():
            if len(node.children) == 2:
                child1, child2 = node.children
                colless_score += abs(len(child1) - len(child2))
            else:
                scores = []
                for j in range(len(node.children)):
                    for k in range(j + 1, len(node.children)):
                        scores.append(abs(len(node.children[j]) - len(node.children[k])))
                colless_score = np.average(scores)
    return colless_score

def sackin(tre):
    """
    Returns sackin metric
    :param tre: ete3.Tree, tree on which these metrics are computed
    :return: float, sackin score computed on the whole tree (sum of this score on all branches)
    """
    sackin_score = 0
    for node in tre.traverse("levelorder"):
        if node.is_leaf():
            sackin_score += int(getattr(node, DEPTH, False))
    return sackin_score

def wd_ratio_delta_w(tre, max_dep):
    """
    Returns two metrics of tree width
    :param tre: ete3.Tree, tree on which these metrics are computed
    :param max_dep: float, maximal depth of tre
    :return: set of two floats, ratio and difference of maximum width and depth
    """
    width_count = np.zeros(max_dep + 1)
    for node in tre.traverse("levelorder"):
        if not node.is_root():
            width_count[int(getattr(node, DEPTH))] += 1
    max_width = max(width_count)
    delta_w = 0
    for j in range(0, len(width_count) - 1):
        if delta_w < abs(width_count[j] - width_count[j - 1]):
            delta_w = abs(width_count[j] - width_count[j - 1])
    return [max_width / max_dep, delta_w]

def max_ladder_il_nodes(tre):
    """
    Returns the maximal ladder score and proportion of internal nodes in ladder
    :param tre: ete3.Tree, tree on which these metrics are computed
    :return: set of two floats, metrics
    """
    max_ladder_score = 0
    il_nodes = 0
    for node in tre.traverse("preorder"):
        if not node.is_leaf():
            if node.ladder > max_ladder_score:
                max_ladder_score = node.ladder
            if node.ladder > 0:
                il_nodes += 1
    res = [None, None]
    try:
        res = [max_ladder_score / len(tre), il_nodes / (len(tre) - 1)]
    except ZeroDivisionError as e:
        print(e)
    finally:
        return res

def staircaseness(tre):
    """
    Returns staircaseness metrics
    :param tre: ete3.Tree, tree on which these metrics are computed
    :return: set of two floats, metrics
    """
    nb_imbalanced_in = 0
    ratio_imbalance = []
    for node in tre.traverse("preorder"):
        if not node.is_leaf():
            if abs(len(node.children[0]) - len(node.children[1])) > 0:
                nb_imbalanced_in += 1
            if len(node.children[0]) > len(node.children[1]):
                ratio_imbalance.append(len(node.children[1]) / len(node.children[0]))
            else:
                ratio_imbalance.append(len(node.children[0]) / len(node.children[1]))
    res = [None, None]
    try:
        res = [nb_imbalanced_in / (len(tre) - 1), np.mean(ratio_imbalance)]
    except ZeroDivisionError as e:
        print(e)
    finally:
        return res

def ltt_plot(tre):
    """
    Returns an event (branching, removal) matrix
    :param tre: ete3.Tree, tree on which these metrics are computed
    :return: np.matrix, branching and removal events
    """
    events = []

    for node in tre.traverse("levelorder"):
        if node.is_leaf():
            events.append([node.dist_to_root, -1, 0])
        else:
            for j in range(1, len(node.children)):
                events.append([node.dist_to_root, 1, 0])

    events = np.asmatrix(events)
    events = np.sort(events.view('i8, i8, i8'), order=['f0'], axis=0).view(float)

    events[0, 2] = float(events[0, 1]) + 1
    for j in np.arange(1, events.shape[0]):
        events[j, 2] = float(events[j - 1, 2]) + float(events[j, 1])

    return events

def ltt_plot_comput(event_mat):
    """
    Returns LTT plot based metrics
    :param event_mat: np.matrix, branching and removal events
    :return: set of 9 floats, LTT plot based metrics
    """
    from scipy.stats import linregress
    # PART1 find max and max time of LTT plot
    events = event_mat.copy()
    max_lineages = 0
    max_lineages_t = 0
    index_slope_change = 0
    for j in range(events.shape[0]):
        if events[j, 2] > max_lineages:
            max_lineages = events[j, 2]
            max_lineages_t = events[j, 0]
            index_slope_change = j

    # PART2 slopes before and after max
    slope_1 = linregress(np.squeeze(events[0:index_slope_change, 2]), np.squeeze(events[0:index_slope_change, 0]))[0]
    slope_2 = linregress(np.squeeze(events[index_slope_change:, 2]), np.squeeze(events[index_slope_change:, 0]))[0]
    slope_rat = slope_1 / slope_2

    all_max = events[-1, 0]

    # PART3 mean sampling time, mean branching times

    # all sampling and branching times
    sampling_times = []
    branching_times_1 = []
    branching_times_2 = []
    branching_times_3 = []
    for j in range(events.shape[0]):
        if events[j, 1] == -1:
            sampling_times.append(events[j, 0])
        elif events[j, 0] < all_max / 3:
            branching_times_1.append(events[j, 0])
        elif events[j, 0] < 2 * all_max / 3:
            branching_times_2.append(events[j, 0])
        else:
            branching_times_3.append(events[j, 0])

    # differences of consecutive sampling/branching times leading to mean sampling and branching (1st, 2nd and 3rd
    # part) times
    diff_samp_times = []
    diff_b_times_1 = []
    diff_b_times_2 = []
    diff_b_times_3 = []

    for j in range(0, len(sampling_times) - 1):
        diff_samp_times.append(sampling_times[j + 1] - sampling_times[j])
    for j in range(0, len(branching_times_1) - 1):
        diff_b_times_1.append(branching_times_1[j + 1] - branching_times_1[j])
    for j in range(0, len(branching_times_2) - 1):
        diff_b_times_2.append(branching_times_2[j + 1] - branching_times_2[j])
    for j in range(0, len(branching_times_3) - 1):
        diff_b_times_3.append(branching_times_3[j + 1] - branching_times_3[j])

    # mean sampling time
    mean_s_time = np.mean(diff_samp_times)
    if len(diff_b_times_1) > 0:
        mean_b_time_1 = np.mean(diff_b_times_1)
    else:
        mean_b_time_1 = 0

    if len(diff_b_times_2) > 0:
        mean_b_time_2 = np.mean(diff_b_times_2)
    else:
        mean_b_time_2 = 0

    if len(diff_b_times_3) > 0:
        mean_b_time_3 = np.mean(diff_b_times_3)
    else:
        mean_b_time_3 = 0

    return [max_lineages, max_lineages_t, slope_1, slope_2, slope_rat, mean_s_time, mean_b_time_1, mean_b_time_2, mean_b_time_3]

def coordinates_comp(events):
    """
    Returns representation of LTT plot under 20 bins (20 x-axis and 20 y axis coordinates)
    :param events: np.matrix, branching and removal events
    :return: list of 40 floats, y- and x-axis coordinates from LTT plot
    """
    # from math import floor
    binscor = np.linspace(0, events.shape[0], 21)
    y_axis = []
    x_axis = []
    for j in range(int(len(binscor) - 1)):
        y_axis.append(np.average(events[int(floor(binscor[j])):int(floor(binscor[j + 1])), 0]))
        x_axis.append(np.average(events[int(floor(binscor[j])):int(floor(binscor[j + 1])), 2]))

    y_axis.extend(x_axis)
    return y_axis

def add_height(tre):
    """
    adds height to each internal node.
    :param tre: ete3.Tree, the tree to which height should be added
    :return: void, modifies the original tree
    """
    for node in tre.traverse('postorder'):
        if node.is_leaf():
            node.add_feature("height", 0)
        else:
            max_child = 0
            for child in node.children:
                if getattr(child, "height", False) > max_child:
                    max_child = getattr(child, "height", False)
            node.add_feature("height", max_child + 1)
    return None

def compute_chain(node, order=4):
    """
    Return a list of shortest descending path from given node (i.e. 'transmission chain'), of given order at maximum
    :param node: ete3.node, node on which the descending path will be computed
    :param order: int, order of transmission chain
    :return: list of floats, of maximum length (order)
    """
    chain = []
    contin = True
    while len(chain) < order and contin:
        children_dist = [child.dist for child in node.children]

        chain.append(min(children_dist))
        node = node.children[children_dist.index(min(children_dist))]
        if node.is_leaf():
            contin = False
    return chain

def compute_chain_stats(tre, order=4):
    """
    Returns mean, min, deciles and max of all transmission chains of given order
    :param tre: ete3.Tree, tree on which these metrics are computed
    :param order: int, order of transmission chain
    :return: list of floats
    """
    chain_sumlengths = []
    for node in tre.traverse():
        if getattr(node, 'height', False) > (order - 1):
            node_chain = compute_chain(node, order=order)
            if len(node_chain) == order:
                chain_sumlengths.append(sum(node_chain))
    sumstats_chain = [len(chain_sumlengths)]
    if len(chain_sumlengths) > 1:
        # mean
        sumstats_chain.append(np.mean(chain_sumlengths))
        # deciles
        sumstats_chain.extend(np.percentile(chain_sumlengths, np.arange(0, 101, 10)))
        # var
        sumstats_chain.append(np.var(chain_sumlengths))
    else:
        sumstats_chain = [0 for _ in range(len(col_chains))]
    return sumstats_chain


import asyncio
import logging
import time
from threading import Thread

import socketio
from socketio.exceptions import ConnectionError
from aiohttp import web

class CObject:
    """
    Mutable object to track some changes in code
    """

    def __init__(self, value: bool):
        self.value = value

    def set(self, value):
        self.value = value

    def __repr__(self):
        return "_cobject value <" + str(self.value) + ">"

    def __bool__(self):
        return self.value

def wait_until_done(condition, delay_t, wait_time: int = 60):
    """
    Wait until the condition turns False before time out
    :param condition: desire condition to track
    :param delay_t: delay time
    :param wait_time: time out
    :return:
    """
    tic_time = time.time()
    while condition:
        time.sleep(delay_t)
        if time.time() - tic_time > wait_time:
            logging.warning("Wait Function Time out")
            return False
    logging.debug("Condition changed . . . !")
    return True

class ServerClass:
    def __init__(self):
        self.components = []
        self.not_finished = CObject(True)
        self.processing = False
        self.sio = socketio.AsyncServer(async_mode="aiohttp")
        self._server_loop = asyncio.new_event_loop()

    @property
    def server_loop(self):
        return self._server_loop

    def start_background_loop(self, loop: asyncio.AbstractEventLoop):
        asyncio.set_event_loop(loop)
        loop.run_forever()

    def run_server(self):
        self.callbacks()
        app = web.Application()
        self.sio.attach(app)
        web.run_app(app, host='0.0.0.0', port=9000)

    async def run_server_2(self):
        app = web.Application()
        self.sio.attach(app)
        runner = web.AppRunner(app)
        self.callbacks()
        await runner.setup()
        site = web.TCPSite(runner, '0.0.0.0', 9000)
        await site.start()
        await asyncio.Event().wait()

    def callbacks(self):

        @self.sio.event
        async def connect(sid, environ):
            logging.info('Socket connected')

        @self.sio.event
        async def disconnect(sid):
            logging.info('Socket disconnected')

        @self.sio.event
        async def connect_error(_):
            logging.error('Connection failed')

        @self.sio.event
        async def response(sid, log):
            logging.info(log)

        @self.sio.event
        async def execute(_):
            self.processing = True
            asyncio.run_coroutine_threadsafe(self.execute__(), self._server_loop)

        @self.sio.event
        async def add(_, data):
            if data:
                logging.info("Received data: {}".format(data))
                await self.add(data)

        @self.sio.event
        async def terminate(_):
            self.processing = False

    async def execute__(self):
        logging.info('Execution started . . .')
        self.processing = True
        self.not_finished.set(True)
        while self.processing:
            # await asyncio.sleep(5)
            time.sleep(1)
            logging.info('want to send result')
            await self.sio.emit('response', 'Results from server')
            logging.info('result sent')
        self.not_finished.set(False)
        logging.info("process stopped")

    async def add(self, data):
        logging.info('adding data')
        await self._add(data)
        logging.info("add another data")
        await self.sio.emit('add_another')

    async def _add(self, data):
        logging.info("want to add data")
        self.stop_processing()
        self.setup_data(data)
        self.start_processing()

    def stop_processing(self):
        logging.info('stop processing')
        if self.processing:
            self.processing = False
            wait_until_done(self.not_finished, 0.1, 10)

    def setup_data(self, data):
        logging.info('setup data . . .')
        self.components.append(data)
        time.sleep(2)

    def start_processing(self):
        asyncio.run_coroutine_threadsafe(self.execute__(), self._server_loop)

if __name__ == '__main__':
    logging.basicConfig(format='%(levelname)s-%(asctime)s::%(message)s', level=logging.DEBUG)
    server = ServerClass()
    Thread(target=server.start_background_loop, args=(server.server_loop,)).start()
    # server.run_server()
    asyncio.run_coroutine_threadsafe(server.run_server_2(), server.server_loop)
    logging.info('*'*80)


import requests
import json

# API URL
base_url = "https://reqres.in/api/users/"

# Read Input From Json File
file = open('/home/srinii/PycharmProjects/Excersise/Users.json')
json_input = file.read()
json_request = json.loads(json_input)
#print(json_request)

# Create POST
response = requests.post(base_url, json_request)
print(response.content)


from xml.etree import ElementTree as ET

from opennsa import constants as cnt
from opennsa.shared import xmlhelper, modifiableresource
from opennsa.discovery.bindings import discovery

ET.register_namespace('nsi', discovery.NSI_DISCOVERY_NS)
ET.register_namespace('gns', discovery.GNS_NS)

class DiscoveryService:

    def __init__(self, nsa_id, version=None, name=None, software_version=None, start_time=None,
                 network_ids=None, interfaces=None, features=None, provider_registry=None,
                 link_vector=None):

        self.nsa_id                 = nsa_id                # string
        self.version                = version               # datetime
        self.name                   = name                  # string
        self.software_version       = software_version      # string
        self.start_time             = start_time            # datetime
        self.network_ids            = network_ids           # [ string ]
        self.interfaces             = interfaces            # [ (type, url, described_by) ]
        self.features               = features              # [ (type, value) ]
        self.provider_registry      = provider_registry     # provreg.ProviderRegistry
        self.link_vector            = link_vector           # linkvector.LinkVector

    def xml(self):

        # location not really supported yet
        interface_types = [ discovery.InterfaceType(i[0], i[1], i[2]) for i in self.interfaces ]
        feature_types   = [ discovery.FeatureType(f[0], f[1]) for f in self.features ]

        peers_with = self.provider_registry.providers.keys()
        peers_with.remove(self.nsa_id)

        topology_vectors = [ (cnt.URN_OGF_PREFIX + tv, cost) for tv, cost in self.link_vector.listVectors().items() ]
        other = discovery.HolderType( [ discovery.Topology(t,c) for (t,c) in topology_vectors ] )

        nsa_element = discovery.NsaType(
            self.nsa_id,
            xmlhelper.createXMLTime(self.version),
            None,
            self.name,
            self.software_version,
            xmlhelper.createXMLTime(self.start_time),
            self.network_ids,
            interface_types,
            feature_types,
            peers_with,
            other,
           )

        e = nsa_element.xml(discovery.nsa)
        payload = ET.tostring(e, 'utf-8')
        return payload

    def resource(self):

        r = modifiableresource.ModifiableResource('DiscoveryService', 'application/xml')
        r.updateResource(self.xml())
        return r


import requests
import redis
import re
import time
import random
import threading
import elasticsearch
from queue import Queue
from datetime import datetime
from elasticsearch import Elasticsearch

from requests.exceptions import HTTPError, ConnectionError

headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36'}
r = redis.StrictRedis(host='192.168.1.157', port=6379, db=0)
es = Elasticsearch("192.168.1.157:9200")
car_number = 0

class My_download_thread(threading.Thread):
    """docstring for download_car"""

    def __init__(self, name):
        threading.Thread.__init__(self)
        self.thread_name = name

    def run(self):
        redis_download_url = r.keys('*')
        redis_download_url_number = r.dbsize()
        while True:
            #redis_download_url_number = r.lrange('renrenche',0 , -1)
            for a in redis_download_url:

                redis_download_url_number   += 1
                print ("线程%s 找到%s台汽车." % (self.thread_name,redis_download_url_number))

                redis_download_url_temp = r.lpop(a)

                c = requests.get(redis_download_url_temp)
                re_car_name = re.compile('<title>(.*)</title>', re.S)
                car_name = re.findall(re_car_name, c.text)
                re_car_money = re.compile(
                    u'(.*)万   建议价')
                car_money = re.findall(re_car_money, c.text)
                data = {
                    "timestamp": datetime.now().strftime("%Y-%m-%dT%H:%M:%S.000+0800"),
                    "car_name": car_name,
                    "car_money": car_money
                }
                es.index(index="car_name", doc_type="car_name", body=data)
            else:
                break

def main():

    for b in range(10):
        s = My_download_thread(b)
        s.start()

if __name__ == '__main__':
    main()


from google.cloud import bigquery
# import os
client = bigquery.Client()

# query = (
#     "SELECT name FROM `bigquery-public-data.usa_names.usa_1910_2013` "
#     'WHERE state = "TX" '
#     "LIMIT 100"
# )

query = (
    'SELECT tags, title, id FROM `bigquery-public-data.stackoverflow.stackoverflow_posts` '
    'WHERE tags LIKE "%java"'
    'LIMIT 10'
)

query_job = client.query(
    query,
    # Location must match that of the dataset(s) referenced in the query.
    location="US",
)  # API request - starts the query

for row in query_job:  # API request - fetches results
    # Row values can be accessed by field name or index
    # assert row[0] == row.name == row["name"]
    print(row['title'])
    print(row['id'])
    print(row['tags'])
    # print('')
    # print('')


'''
__author__: Federico Pretini
'''
from PyQt5.QtWidgets import QMainWindow, QTableWidgetItem, QMessageBox
from PyQt5 import QtGui, QtCore

from bar.view.Ui_AggiornaListinoView import Ui_AggiornaListinoView

class AggiornaListinoView(QMainWindow, Ui_AggiornaListinoView):
    def __init__(self, log, controller, callback, parent=None):
        super(AggiornaListinoView, self).__init__(parent)
        self.setupUi(self)
        self.log = log
        self.controller = controller
        self.callback = callback
        self.listino_bar=[]
        self.update_tableBar()
        self.update_cB_categoria()
        self.connect_all()

    def connect_all(self):
        self.pB_aggiungi.clicked.connect(lambda: self.aggiungi_consumazione())
        self.pB_elimina.clicked.connect(lambda: self.elimina_consumazione())
        self.pB_annulla.clicked.connect(lambda: self.annulla_consumazione())
        self.tableWidget_ListinoBar.itemSelectionChanged.connect(lambda: self.table_selected())

    def update_listinoBar(self):
        self.controller.update_liste()
        self.listino_bar = [self.controller.get_lista_alcolici(), self.controller.get_lista_analcolici(), self.controller.get_lista_bibite(),
                            self.controller.get_lista_caffetteria(), self.controller.get_lista_aperitivi(), self.controller.get_lista_vini(),
                            self.controller.get_lista_liquori(), self.controller.get_lista_pasticceria()]

    def update_tableBar(self):
        def get_item(info):
            item = QTableWidgetItem(str(info))
            font = QtGui.QFont()
            font.setBold(False)
            font.setFamily("Arial")
            font.setPointSize(12)
            item.setFont(font)
            item.setTextAlignment(QtCore.Qt.AlignCenter)
            return item

        self.update_listinoBar()
        self.tableWidget_ListinoBar.setRowCount(0)
        self.listino_bar.reverse()  #Aggiorna la tabella a partire dagli alcolici
        for categoria in self.listino_bar:
            i = 0
            for consumazione in categoria:
                self.tableWidget_ListinoBar.insertRow(i)
                j = 0
                for info in consumazione:
                    self.tableWidget_ListinoBar.setItem(i,j, get_item(info))
                    j+=1
                i+=1

    def update_cB_categoria(self):
        lista_categoria = set([""]) #Utlizzo il tipo set che non può avere duplicati al suo interno
        for categoria in self.listino_bar:
            for consumazione in categoria:
                lista_categoria.add(consumazione[1])
        self.cB_categoria.addItems(lista_categoria)

    def table_selected(self):
        if len(self.tableWidget_ListinoBar.selectionModel().selectedRows()) > 0:
            self.pB_elimina.setEnabled(True)
        else:
            self.pB_elimina.setEnabled(False)

    def aggiungi_consumazione(self):
        nome_consumazione = self.lineE_nome.text()
        categoria = self.cB_categoria.currentText()
        prezzo = self.spinB_prezzo.value()
        if nome_consumazione == "" or categoria == "" or prezzo == 0:
            QMessageBox.critical(self, "Errore", "Inserisci tutti i dati richiesti prima di premere aggiungi")
            return
        self.controller.aggiungi_consumazione([nome_consumazione, categoria, prezzo])
        self.log.print_log_add("aggiunta consumazione bar")
        self.callback() #Aggiorna le comboBox sull'altra interfaccia
        self.update_tableBar() #Aggiorna la lista della mio listino bar
        self.annulla_consumazione()

    def elimina_consumazione(self):
        item = self.tableWidget_ListinoBar.selectedItems()
        lista_nomi = []
        contatore = 0
        for info in item:
            if contatore%3 == 0:
              lista_nomi.append(info.text())
            contatore+=1
        self.controller.elimina_consumazione(lista_nomi)
        self.log.print_log_delete("eliminata consumazione bar")
        self.callback()
        self.update_tableBar()

    def annulla_consumazione(self):
        self.cB_categoria.setCurrentIndex(0)
        self.lineE_nome.clear()
        self.spinB_prezzo.setValue(0)


import camelot
import pandas as pd

pd.set_option('display.max_columns', None)
pd.set_option('display.max_row', None)
pd.set_option('display.width', None)

def table_trim(path):
    tables = camelot.read_pdf(path, pages="all", kind= "line")
    keys, result = [], []
    print(tables)
    # # 테이블 수 만큼 반복
    # for table in tables:
    #     title_idx = []
    #     frame = table.df
    #     print(frame)
    # return result

def extract(frame):
    result = {}
    for f in frame:
        if f.iloc[0][0] == '표제부':
            val = f.iloc[2][2].replace('\n', ' ')
            if '[도로명주소]' in val:
                result['소재지'] = val.split(' [도로명주소] ')[0]
                result['도로명주소'] = val.split(' [도로명주소] ')[1]

            if f.iloc[1][2] == '건 물 번 호':
                result['소재지'] = "%s %s" % (result['소재지'], f.iloc[2][2])
                result['도로명주소'] = "%s %s" % (result['도로명주소'], f.iloc[2][2])

            if f.iloc[1][4] == '권리자 및 기타사항':
                owners = f.iloc[2][4]
                if '지분' in owners:
                    owners = owners.split('\n')
                    extract_owners, name, share = [], '', ''

                    for o in owners:
                        if '지분' in o:
                            share = o.split('지분 ')[1]
                        elif '*' in o:
                            name = '%s/%s' % (o.split('  ')[0], o.split('  ')[1].split('-')[0])
                        if name and share:
                            extract_owners.append("%s/%s" % (name, share))
                            name, share = '', ''

                    result['소유자'] = extract_owners
                else:
                    result['소유자'] = owners.split('소유자  ')[1].split('-')[0].replace('  ', '/')

        if f.iloc[0][0] == '대지권의목적인토지의표시':
            for i, s in enumerate(f.iloc[1]):
                if s == '대지권종류': result['대지권종류'] = f.iloc[2][i]
                elif s == '대지권비율': result['대지권비율'] = f.iloc[2][i]

    print(result['소유자'])

file_name = 'bd_test.pdf'
table_trim(file_name)


"""
client.py module scrapes sport results from the site https://www.fonbet.ru/results/
The results are stored in Redis.
"""
import asyncio
import aiohttp
import datetime
import redis

now = round(datetime.datetime.now().timestamp())
req = {
    'url': 'https://clientsapi31.bkfon-resource.ru/results/results.json.php',
    'params': {
        'locale': 'en',
        'lastUpdate': 0,
        '_': now,
    }
}
redis_events = redis.Redis(host='localhost', port=6379, db=1)
redis_sections = redis.Redis(host='localhost', port=6379, db=2)

async def main():
    async with aiohttp.ClientSession() as session:
        async with session.post(req['url'], params=req['params']) as response:
            results = await response.json()
            # store events to redis
            for event in results['events']:
                event_id = event['id']
                del event['id']
                redis_events.hmset(event_id, event)
            # store sections to redis
            for section in results['sections']:
                section_name = section['name']
                redis_sections.delete(section_name)
                redis_sections.rpush(section_name, *section['events'])

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())


"""
A DND-based LSTM based on ...
Ritter, et al. (2018).
Been There, Done That: Meta-Learning with Episodic Recall.
Proceedings of the International Conference on Machine Learning (ICML).
"""
import torch
import torch.nn as nn
from model.DND import DND
from model.A2C import A2C_linear

# constants
N_GATES = 4

class DNDLSTM(nn.Module):

    def __init__(self,
            input_dim, hidden_dim, output_dim,
            dict_len,
            exp_settings,
            bias=True
    ):
        super(DNDLSTM, self).__init__()
        self.ctx_dim = exp_settings['ctx_dim']
        self.obs_dim = exp_settings['obs_dim']
        self.input_dim = input_dim
        self.hidden_dim = hidden_dim
        self.bias = bias
        self.exp_settings = exp_settings
        # input-hidden weights
        self.i2h = nn.Linear(input_dim, (N_GATES+1) * hidden_dim, bias=bias)
        # hidden-hidden weights
        self.h2h = nn.Linear(hidden_dim, (N_GATES+1) * hidden_dim, bias=bias)
        # dnd
        self.dnd = DND(dict_len, hidden_dim, exp_settings)
        #policy
        self.a2c = A2C_linear(hidden_dim, output_dim)
        # init
        self.reset_parameter()

    def reset_parameter(self):
        for name, wts in self.named_parameters():
            if 'weight' in name:
                torch.nn.init.orthogonal_(wts)
            elif 'bias' in name:
                torch.nn.init.constant_(wts, 0)

    def forward(self, x_t, h, c):
        # unpack activity
        h = h.view(h.size(1), -1)
        c = c.view(c.size(1), -1)

        # Input is the obs/context paired together
        x_t = x_t.view(x_t.size(1), -1)

        # Split the input for possible use later
        observation = x_t[0][:self.obs_dim].view(1, self.obs_dim)
        context = x_t[0][self.obs_dim:].view(1, self.ctx_dim)
        # print('Obs: ', observation)
        # print('CTX: ', context)

        # Only passing the observations into the model ()
        if self.exp_settings['agent_input'] == 'obs':
            x_t = observation

        # transform the input info
        Wx = self.i2h(x_t)
        Wh = self.h2h(h)
        preact = Wx + Wh
        # get all gate values
        gates = preact[:, : N_GATES * self.hidden_dim].sigmoid()
        # split input(write) gate, forget gate, output(read) gate
        f_t = gates[:, :self.hidden_dim]
        i_t = gates[:, self.hidden_dim:2 * self.hidden_dim]
        o_t = gates[:, 2*self.hidden_dim:3 * self.hidden_dim]
        r_t = gates[:, -self.hidden_dim:]
        # stuff to be written to cell state
        c_t_new = preact[:, N_GATES * self.hidden_dim:].tanh()
        # new cell state = gated(prev_c) + gated(new_stuff)
        c_t = torch.mul(f_t, c) + torch.mul(i_t, c_t_new)

        # What portion of the input is being used in memory
        mem_store = self.exp_settings['mem_store']

        # Query Memory
        ########################
        # All input is stored in memory (QiHong Github version)
        if mem_store == 'obs/context':
            m_t = self.dnd.get_memory(x_t, context).tanh()

        # Only context is stored in memory (Ritter Version)
        elif mem_store == 'context':
            m_t = self.dnd.get_memory(context, context).tanh()

        # Hidden States stored in memory (Our Version)
        else: #mem_store == 'hidden'
            m_t = self.dnd.get_memory(h, context).tanh()
        #######################

        # gate the memory; in general, can be any transformation of it
        c_t = c_t + torch.mul(r_t, m_t)
        # get gated hidden state from the cell state
        h_t = torch.mul(o_t, c_t.tanh())

        # Saving Memory
        ########################
        # All input is stored in memory (QiHong Github version)
        if mem_store == 'obs/context':
            self.dnd.save_memory(x_t, c_t)

        # Only context is stored in memory (Ritter Version)
        elif mem_store == 'context':
            self.dnd.save_memory(context, c_t)

        # Hidden States stored in memory (Our Version)
        else: #mem_store == 'hidden'
            self.dnd.save_memory(h_t, c_t)
        #######################

        # policy
        pi_a_t, v_t = self.a2c.forward(h_t)
        # pick an action
        a_t, prob_a_t = self.pick_action(pi_a_t)
        # reshape data
        h_t = h_t.view(1, h_t.size(0), -1)
        c_t = c_t.view(1, c_t.size(0), -1)
        # fetch activity
        output = [a_t, prob_a_t, v_t, h_t, c_t]
        cache = [f_t, i_t, o_t, r_t, m_t]
        return output, cache

    def pick_action(self, action_distribution):
        """action selection by sampling from a multinomial.

        Parameters
        ----------
        action_distribution : 1d torch.tensor
            action distribution, pi(a|s)

        Returns
        -------
        torch.tensor(int), torch.tensor(float)
            sampled action, log_prob(sampled action)
        """
        m = torch.distributions.Categorical(action_distribution)
        a_t = m.sample()
        log_prob_a_t = m.log_prob(a_t)
        return a_t, log_prob_a_t

    def get_init_states(self, scale=.1):
        h_0 = torch.randn(1, 1, self.hidden_dim) * scale
        c_0 = torch.randn(1, 1, self.hidden_dim) * scale
        return h_0, c_0

    def flush_trial_buffer(self):
        self.dnd.trial_buffer = [()]

    def turn_off_encoding(self):
        self.dnd.encoding_off = True

    def turn_on_encoding(self):
        self.dnd.encoding_off = False

    def turn_off_retrieval(self):
        self.dnd.retrieval_off = True

    def turn_on_retrieval(self):
        self.dnd.retrieval_off = False

    def reset_memory(self):
        self.dnd.reset_memory()

    def get_all_mems(self):
        n_mems = len(self.dnd.keys)
        K = [self.dnd.keys[i] for i in range(n_mems)]
        V = [self.dnd.vals[i] for i in range(n_mems)]
        return K, V

    def get_all_mems_josh(self):
        # Memory now storing lists of memories in every spot, so we only want the most updated version of each list
        n_mems = len(self.dnd.keys)
        K = [self.dnd.keys[i][0] for i in range(n_mems)]
        V = [self.dnd.vals[i] for i in range(n_mems)]
        return K, V


from tempfile import mkdtemp
from excel_sheet_integrator import LocalExcelSheetIntegrator

from flask import (
    Flask,
    flash,
    redirect,
    render_template,
    request,
    safe_join,
    send_file,
)
from werkzeug.exceptions import NotFound, BadRequest
from werkzeug.utils import secure_filename

UPLOAD_FOLDER = mkdtemp()

NO_FILE_MSG = "Missing file in request"
NO_FILE_SELECTED_MSG = "No file selected for uploading"
SUCCESSFUL_UPLOAD = "File successfully uploaded"

app = Flask(__name__)
app.secret_key = "setec astronomy"
app.config["SESSION_TYPE"] = "filesystem"
app.config["UPLOAD_FOLDER"] = UPLOAD_FOLDER

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

@app.route("/upload", methods=["POST"])
def upload_file_locally():
    if request.method == "POST":
        file_path = handle_local_file(request.files["file"])
        master_file = request.form["master_sheet"]

        excel_sheet_integrator = LocalExcelSheetIntegrator(file_path)
        excel_sheet_integrator.create_new_excel_sheet(master_file)

        return redirect("/download/{0}".format(file_path))
    return redirect('/')

@app.route("/download/<path:file_path>", methods=["GET"])
def download_file(file_path):
    try:
        return send_file(file_path)
    except (NotFound, BadRequest):
        return redirect("/")

def handle_local_file(file):
    file_name = get_file_name(file)

    file_path = safe_join(app.config["UPLOAD_FOLDER"], file_name)
    file.save(file_path)
    flash(SUCCESSFUL_UPLOAD)
    return file_path

def get_file_name(file):
    return secure_filename(file.filename)

if __name__ == "__main__":
    app.run(debug=False, port=8080)


from collections import defaultdict
from copy import copy
from typing import List, Tuple, Set, Dict
from pathlib import Path

INPUT_FILE = Path(__file__).with_suffix(".input")

EXAMPLE_DATA = """0/2
2/2
2/3
3/4
3/5
0/1
10/1
9/10"""

def load_pieces(input_str: str) -> Set[Tuple[int, int]]:
    ret_list = set()
    for line in input_str.split("\n"):
        ret_list.add(tuple(int(i) for i in line.split("/")))
    # ret_list.sort(key=sum, reverse=True)
    return ret_list

def calc_strength(bridge_list: List[Tuple[int, int]]) -> int:
    return sum(sum(p) for p in bridge_list)

def gen_matching(
    prev_con: int, remaining: Set[Tuple[int, int]], cached_mapping: Dict[int, List[Tuple[int, int]]] = defaultdict(list)
):
    if not cached_mapping:  # remaining must contain all possible items first run otherwise cache will be bad
        for part in remaining:
            cached_mapping[part[0]].append(part)
            if part[0] == part[1]:
                continue
            cached_mapping[part[1]].append(part)
    for part in cached_mapping[prev_con]:
        if part in remaining:
            yield part

def build_bridges(
    prev_piece: int,
    so_far: List[Tuple[int, int]],
    remaining: Set[Tuple[int, int]],
    part1: List[List[Tuple[int, int]]],
    part2: List[List[Tuple[int, int]]],
):
    leaf = True
    for possible_next in gen_matching(prev_piece, remaining):
        leaf = False
        new_bridge = copy(so_far)
        reduced = copy(remaining)
        reduced.remove(possible_next)
        next_piece = possible_next[0]
        if next_piece == prev_piece:
            next_piece = possible_next[1]
        new_bridge.append(possible_next)
        build_bridges(next_piece, new_bridge, reduced, part1, part2)

    if leaf:
        if calc_strength(so_far) > calc_strength(part1[0]):
            part1[:] = [so_far]

        if len(so_far) > len(part2[0]):
            part2[:] = [so_far]
        elif len(so_far) == len(part2[0]):
            if calc_strength(so_far) > calc_strength(part2[0]):
                part2[:] = [so_far]

if __name__ == "__main__":
    DATA = INPUT_FILE.read_text().strip()
    piece_list = load_pieces(DATA)
    print(piece_list)
    completed_bridges_p1 = [[]]
    completed_bridges_p2 = [[]]
    build_bridges(0, [], piece_list, completed_bridges_p1, completed_bridges_p2)
    print(completed_bridges_p1)
    print(calc_strength(completed_bridges_p1[0]))
    print(completed_bridges_p2)
    print(calc_strength(completed_bridges_p2[0]))


import picamera
import time
import datetime as dt

parent_directory = '/home/pi/Desktop/Test/2023-09-27'
date='2023_09-27'
exp='large-slide_rigV2_07mm_08agarose_needle-top_limited-FOV_29Larvae_framerate1_4hr_video4'
lighting = 'white'
lighting_paradigm = 'ON'
experiment_name = parent_directory + '/' + date + '_' + lighting + '_' + lighting_paradigm + '_'+ exp

experiment_duration = 14400

with picamera.PiCamera() as camera:
        camera.resolution = (1200, 1200)
        #camera.color_effects=(128,128) #Black and white
        camera.framerate = 1
        camera.rotation = 90
        #camera.annotate_background = picamera.Color('black')
        camera.start_preview(alpha=255)
        input('Press ENTER to start recording')
        start = dt.datetime.now()
        camera.start_recording(experiment_name + ".h264")
        while not (dt.datetime.now() - start).total_seconds() > experiment_duration:
            camera.annotate_text = str((dt.datetime.now()-start).total_seconds())
        camera.stop_recording()