"""
==========
Quick Menu
==========

A :class:`QuickMenu` widget provides lists of actions organized in tabs
with a quick search functionality.

"""

import sys
import logging

from collections import namedtuple, Callable

import numpy

from PyQt4.QtGui import (
    QWidget, QFrame, QToolButton, QAbstractButton, QAction, QIcon, QTreeView,
    QButtonGroup, QStackedWidget, QHBoxLayout, QVBoxLayout, QSizePolicy,
    QStandardItemModel, QSortFilterProxyModel, QStyleOptionToolButton,
    QStylePainter, QStyle, QApplication, QStyledItemDelegate,
    QStyleOptionViewItemV4, QSizeGrip, QPolygon, QRegion, QItemSelectionModel,
    QBrush
)

from PyQt4.QtCore import pyqtSignal as Signal
from PyQt4.QtCore import pyqtProperty as Property

from PyQt4.QtCore import (
    Qt, QObject, QPoint, QSize, QRect, QEventLoop, QEvent, QModelIndex,
    QTimer
)

from ..gui.framelesswindow import FramelessWindow
from ..gui.lineedit import LineEdit
from ..gui.tooltree import ToolTree, FlattenedTreeItemModel
from ..gui.utils import StyledWidget_paintEvent, create_css_gradient
from ..utils import qtcompat
from ..registry.qt import QtWidgetRegistry

from ..resources import icon_loader

log = logging.getLogger(__name__)

class _MenuItemDelegate(QStyledItemDelegate):
    def __init__(self, parent=None):
        QStyledItemDelegate.__init__(self, parent)

    def sizeHint(self, option, index):
        option = QStyleOptionViewItemV4(option)
        self.initStyleOption(option, index)
        size = QStyledItemDelegate.sizeHint(self, option, index)

        # TODO: get the default QMenu item height from the current style.
        size.setHeight(max(size.height(), 25))
        return size

class MenuPage(ToolTree):
    """
    A menu page in a :class:`QuickMenu` widget, showing a list of actions.
    Shown actions can be disabled by setting a filtering function using the
    :func:`setFilterFunc`.

    """
    def __init__(self, parent=None, title=None, icon=None, **kwargs):
        ToolTree.__init__(self, parent, **kwargs)

        if title is None:
            title = ""

        if icon is None:
            icon = QIcon()

        self.__title = title
        self.__icon = icon
        self.__sizeHint = None

        self.view().setItemDelegate(_MenuItemDelegate(self.view()))
        self.view().entered.connect(self.__onEntered)
        self.view().viewport().setMouseTracking(True)

        # Make sure the initial model is wrapped in a ItemDisableFilter.
        self.setModel(self.model())

    def setTitle(self, title):
        """
        Set the title of the page.
        """
        if self.__title != title:
            self.__title = title
            self.update()

    def title(self):
        """
        Return the title of this page.
        """
        return self.__title

    title_ = Property(unicode, fget=title, fset=setTitle,
                      doc="Title of the page.")

    def setIcon(self, icon):
        """
        Set icon for this menu page.
        """
        if self.__icon != icon:
            self.__icon = icon
            self.update()

    def icon(self):
        """
        Return the icon of this manu page.
        """
        return self.__icon

    icon_ = Property(QIcon, fget=icon, fset=setIcon,
                     doc="Page icon")

    def setFilterFunc(self, func):
        """
        Set the filtering function. `func` should a function taking a single
        :class:`QModelIndex` argument and returning True if the item at index
        should be disabled and False otherwise. To disable filtering `func` can
        be set to ``None``.

        """
        proxyModel = self.view().model()
        proxyModel.setFilterFunc(func)

    def setModel(self, model):
        """
        Reimplemented from :func:`ToolTree.setModel`.
        """
        proxyModel = ItemDisableFilter(self)
        proxyModel.setSourceModel(model)
        ToolTree.setModel(self, proxyModel)

        self.__invalidateSizeHint()

    def setRootIndex(self, index):
        """
        Reimplemented from :func:`ToolTree.setRootIndex`
        """
        proxyModel = self.view().model()
        mappedIndex = proxyModel.mapFromSource(index)
        ToolTree.setRootIndex(self, mappedIndex)

        self.__invalidateSizeHint()

    def rootIndex(self):
        """
        Reimplemented from :func:`ToolTree.rootIndex`
        """
        proxyModel = self.view().model()
        return proxyModel.mapToSource(ToolTree.rootIndex(self))

    def sizeHint(self):
        """
        Reimplemented from :func:`QWidget.sizeHint`.
        """
        if self.__sizeHint is None:
            view = self.view()
            model = view.model()

            # This will not work for nested items (tree).
            count = model.rowCount(view.rootIndex())

            # 'sizeHintForColumn' is the reason for size hint caching
            # since it must traverse all items in the column.
            width = view.sizeHintForColumn(0)

            if count:
                height = view.sizeHintForRow(0)
                height = height * count
            else:
                height = 0
            self.__sizeHint = QSize(width, height)

        return self.__sizeHint

    def __invalidateSizeHint(self):
        self.__sizeHint = None
        self.updateGeometry()

    def __onEntered(self, index):
        if not index.isValid():
            return

        if self.view().state() != QTreeView.NoState:
            # The item view can emit an 'entered' signal while the model/view
            # is being changed (rows removed). When this happens, setting the
            # current item can segfault (in QTreeView::scrollTo).
            return

        if index.flags() & Qt.ItemIsEnabled:
            self.view().selectionModel().setCurrentIndex(
                index,
                QItemSelectionModel.ClearAndSelect
            )

class ItemDisableFilter(QSortFilterProxyModel):
    """
    An filter proxy model used to disable selected items based on
    a filtering function.

    """
    def __init__(self, parent=None):
        QSortFilterProxyModel.__init__(self, parent)

        self.__filterFunc = None

    def setFilterFunc(self, func):
        """
        Set the filtering function.
        """
        if not (isinstance(func, Callable) or func is None):
            raise ValueError("A callable object or None expected.")

        if self.__filterFunc != func:
            self.__filterFunc = func
            # Mark the whole model as changed.
            self.dataChanged.emit(self.index(0, 0),
                                  self.index(self.rowCount(), 0))

    def flags(self, index):
        """
        Reimplemented from :class:`QSortFilterProxyModel.flags`
        """
        source = self.mapToSource(index)
        flags = source.flags()

        if self.__filterFunc is not None:
            enabled = flags & Qt.ItemIsEnabled
            if enabled and not self.__filterFunc(source):
                flags ^= Qt.ItemIsEnabled

        return flags

class SuggestMenuPage(MenuPage):
    """
    A MenuMage for the QuickMenu widget supporting item filtering
    (searching).

    """
    def __init__(self, *args, **kwargs):
        MenuPage.__init__(self, *args, **kwargs)

    def setModel(self, model):
        """
        Reimplmemented from :ref:`MenuPage.setModel`.
        """
        flat = FlattenedTreeItemModel(self)
        flat.setSourceModel(model)
        flat.setFlatteningMode(flat.InternalNodesDisabled)
        flat.setFlatteningMode(flat.LeavesOnly)
        proxy = SortFilterProxyModel(self)
        proxy.setFilterCaseSensitivity(False)
        proxy.setSourceModel(flat)
        ToolTree.setModel(self, proxy)
        self.ensureCurrent()

    def setFilterFixedString(self, pattern):
        """
        Set the fixed string filtering pattern. Only items which contain the
        `pattern` string will be shown.

        """
        proxy = self.view().model()
        proxy.setFilterFixedString(pattern)
        self.ensureCurrent()

    def setFilterRegExp(self, pattern):
        """
        Set the regular expression filtering pattern. Only items matching
        the `pattern` expression will be shown.

        """
        filter_proxy = self.view().model()
        filter_proxy.setFilterRegExp(pattern)
        self.ensureCurrent()

    def setFilterWildCard(self, pattern):
        """
        Set a wildcard filtering pattern.
        """
        filter_proxy = self.view().model()
        filter_proxy.setFilterWildCard(pattern)
        self.ensureCurrent()

    def setFilterFunc(self, func):
        """
        Set a filtering function.
        """
        filter_proxy = self.view().model()
        filter_proxy.setFilterFunc(func)

class SortFilterProxyModel(QSortFilterProxyModel):
    """
    An filter proxy model used to filter items based on a filtering
    function.

    """
    def __init__(self, parent=None):
        QSortFilterProxyModel.__init__(self, parent)

        self.__filterFunc = None

    def setFilterFunc(self, func):
        """
        Set the filtering function.
        """
        if not (isinstance(func, Callable) or func is None):
            raise ValueError("A callable object or None expected.")

        if self.__filterFunc is not func:
            self.__filterFunc = func
            self.invalidateFilter()

    def filterFunc(self):
        return self.__filterFunc

    def filterAcceptsRow(self, row, parent=QModelIndex()):
        accepted = QSortFilterProxyModel.filterAcceptsRow(self, row, parent)
        if accepted and self.__filterFunc is not None:
            model = self.sourceModel()
            index = model.index(row, self.filterKeyColumn(), parent)
            return self.__filterFunc(index)
        else:
            return accepted

class SearchWidget(LineEdit):
    def __init__(self, parent=None, **kwargs):
        LineEdit.__init__(self, parent, **kwargs)
        self.__setupUi()

    def __setupUi(self):
        icon = icon_loader().get("icons/Search.svg")
        action = QAction(icon, "Search", self)
        self.setAction(action, LineEdit.LeftPosition)

class MenuStackWidget(QStackedWidget):
    """
    Stack widget for the menu pages.
    """

    def sizeHint(self):
        """
        Size hint is the maximum width and median height of the widgets
        contained in the stack.

        """
        default_size = QSize(200, 400)
        widget_hints = [default_size]
        for i in range(self.count()):
            hint = self.widget(i).sizeHint()
            widget_hints.append(hint)

        width = max([s.width() for s in widget_hints])
        # Take the median for the height
        height = numpy.median([s.height() for s in widget_hints])

        return QSize(width, int(height))

    def __sizeHintForTreeView(self, view):
        hint = view.sizeHint()
        model = view.model()

        count = model.rowCount()
        width = view.sizeHintForColumn(0)

        if count:
            height = view.sizeHintForRow(0)
            height = height * count
        else:
            height = hint.height()

        return QSize(max(width, hint.width()), max(height, hint.height()))

class TabButton(QToolButton):
    def __init__(self, parent=None, **kwargs):
        QToolButton.__init__(self, parent, **kwargs)
        self.setToolButtonStyle(Qt.ToolButtonIconOnly)
        self.setCheckable(True)

        self.__flat = True
        self.__showMenuIndicator = False

    def setFlat(self, flat):
        if self.__flat != flat:
            self.__flat = flat
            self.update()

    def flat(self):
        return self.__flat

    flat_ = Property(bool, fget=flat, fset=setFlat,
                     designable=True)

    def setShownMenuIndicator(self, show):
        if self.__showMenuIndicator != show:
            self.__showMenuIndicator = show
            self.update()

    def showMenuIndicator(self):
        return self.__showMenuIndicator

    showMenuIndicator_ = Property(bool, fget=showMenuIndicator,
                                  fset=setShownMenuIndicator,
                                  designable=True)

    def paintEvent(self, event):
        opt = QStyleOptionToolButton()
        self.initStyleOption(opt)
        if self.__showMenuIndicator and self.isChecked():
            opt.features |= QStyleOptionToolButton.HasMenu
        if self.__flat:
            # Use default widget background/border styling.
            StyledWidget_paintEvent(self, event)

            p = QStylePainter(self)
            p.drawControl(QStyle.CE_ToolButtonLabel, opt)
        else:
            p = QStylePainter(self)
            p.drawComplexControl(QStyle.CC_ToolButton, opt)

    def sizeHint(self):
        opt = QStyleOptionToolButton()
        self.initStyleOption(opt)
        if self.__showMenuIndicator and self.isChecked():
            opt.features |= QStyleOptionToolButton.HasMenu
        style = self.style()

        hint = style.sizeFromContents(QStyle.CT_ToolButton, opt,
                                      opt.iconSize, self)
        return hint

_Tab = namedtuple(
    "_Tab",
    ["text",
     "icon",
     "toolTip",
     "button",
     "data",
     "palette"]
)

class TabBarWidget(QWidget):
    """
    A vertical tab bar widget using tool buttons as for tabs.
    """

    currentChanged = Signal(int)

    def __init__(self, parent=None, **kwargs):
        QWidget.__init__(self, parent, **kwargs)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(0)
        self.setLayout(layout)

        self.setSizePolicy(QSizePolicy.Fixed,
                           QSizePolicy.Expanding)
        self.__tabs = []

        self.__currentIndex = -1
        self.__changeOnHover = False

        self.__iconSize = QSize(26, 26)

        self.__group = QButtonGroup(self, exclusive=True)
        self.__group.buttonPressed[QAbstractButton].connect(
            self.__onButtonPressed
        )
        self.setMouseTracking(True)

        self.__sloppyButton = None
        self.__sloppyRegion = QRegion()
        self.__sloppyTimer = QTimer(self, singleShot=True)
        self.__sloppyTimer.timeout.connect(self.__onSloppyTimeout)

    def setChangeOnHover(self, changeOnHover):
        """
        If set to ``True`` the tab widget will change the current index when
        the mouse hovers over a tab button.

        """
        if self.__changeOnHover != changeOnHover:
            self.__changeOnHover = changeOnHover

    def changeOnHover(self):
        """
        Does the current tab index follow the mouse cursor.
        """
        return self.__changeOnHover

    def count(self):
        """
        Return the number of tabs in the widget.
        """
        return len(self.__tabs)

    def addTab(self, text, icon=None, toolTip=None):
        """
        Add a new tab and return it's index.
        """
        return self.insertTab(self.count(), text, icon, toolTip)

    def insertTab(self, index, text, icon=None, toolTip=None):
        """
        Insert a tab at `index`
        """
        button = TabButton(self, objectName="tab-button")
        button.setSizePolicy(QSizePolicy.Expanding,
                             QSizePolicy.Expanding)
        button.setIconSize(self.__iconSize)
        button.setMouseTracking(True)

        self.__group.addButton(button)

        button.installEventFilter(self)

        tab = _Tab(text, icon, toolTip, button, None, None)
        self.layout().insertWidget(index, button)

        self.__tabs.insert(index, tab)
        self.__updateTab(index)

        if self.currentIndex() == -1:
            self.setCurrentIndex(0)
        return index

    def removeTab(self, index):
        """
        Remove a tab at `index`.
        """
        if index >= 0 and index < self.count():
            tab = self.__tabs.pop(index)
            layout_index = self.layout().indexOf(tab.button)
            if layout_index != -1:
                self.layout().takeAt(layout_index)

            self.__group.removeButton(tab.button)

            tab.button.removeEventFilter(self)

            if tab.button is self.__sloppyButton:
                self.__sloppyButton = None
                self.__sloppyRegion = QRegion()

            tab.button.deleteLater()
            tab.button.setParent(None)

            if self.currentIndex() == index:
                if self.count():
                    self.setCurrentIndex(max(index - 1, 0))
                else:
                    self.setCurrentIndex(-1)

    def setTabIcon(self, index, icon):
        """
        Set the `icon` for tab at `index`.
        """
        self.__tabs[index] = self.__tabs[index]._replace(icon=icon)
        self.__updateTab(index)

    def setTabToolTip(self, index, toolTip):
        """
        Set `toolTip` for tab at `index`.
        """
        self.__tabs[index] = self.__tabs[index]._replace(toolTip=toolTip)
        self.__updateTab(index)

    def setTabText(self, index, text):
        """
        Set tab `text` for tab at `index`
        """
        self.__tabs[index] = self.__tabs[index]._replace(text=text)
        self.__updateTab(index)

    def setTabPalette(self, index, palette):
        """
        Set the tab button palette.
        """
        self.__tabs[index] = self.__tabs[index]._replace(palette=palette)
        self.__updateTab(index)

    def setCurrentIndex(self, index):
        """
        Set the current tab index.
        """
        if self.__currentIndex != index:
            self.__currentIndex = index

            self.__sloppyRegion = QRegion()
            self.__sloppyButton = None

            if index != -1:
                self.__tabs[index].button.setChecked(True)

            self.currentChanged.emit(index)

    def currentIndex(self):
        """
        Return the current index.
        """
        return self.__currentIndex

    def button(self, index):
        """
        Return the `TabButton` instance for index.
        """
        return self.__tabs[index].button

    def setIconSize(self, size):
        if self.__iconSize != size:
            self.__iconSize = size
            for tab in self.__tabs:
                tab.button.setIconSize(self.__iconSize)

    def __updateTab(self, index):
        """
        Update the tab button.
        """
        tab = self.__tabs[index]
        b = tab.button

        if tab.text:
            b.setText(tab.text)

        if tab.icon is not None and not tab.icon.isNull():
            b.setIcon(tab.icon)

        if tab.palette:
            b.setPalette(tab.palette)

    def __onButtonPressed(self, button):
        for i, tab in enumerate(self.__tabs):
            if tab.button is button:
                self.setCurrentIndex(i)
                break

    def __calcSloppyRegion(self, current):
        """
        Given a current mouse cursor position return a region of the widget
        where hover/move events should change the current tab only on a
        timeout.

        """
        p1 = current + QPoint(0, 2)
        p2 = current + QPoint(0, -2)
        p3 = self.pos() + QPoint(self.width()+10, 0)
        p4 = self.pos() + QPoint(self.width()+10, self.height())
        return QRegion(QPolygon([p1, p2, p3, p4]))

    def __setSloppyButton(self, button):
        """
        Set the current sloppy button (a tab button inside sloppy region)
        and reset the sloppy timeout.

        """
        if not button.isChecked():
            self.__sloppyButton = button
            delay = self.style().styleHint(QStyle.SH_Menu_SubMenuPopupDelay, None)
            # The delay timeout is the same as used by Qt in the QMenu.
            self.__sloppyTimer.start(delay)
        else:
            self.__sloppyTimer.stop()

    def __onSloppyTimeout(self):
        if self.__sloppyButton is not None:
            button = self.__sloppyButton
            self.__sloppyButton = None
            if not button.isChecked():
                index = [tab.button for tab in self.__tabs].index(button)
                self.setCurrentIndex(index)

    def eventFilter(self, receiver, event):
        if event.type() == QEvent.MouseMove and \
                isinstance(receiver, TabButton):
            pos = receiver.mapTo(self, event.pos())
            if self.__sloppyRegion.contains(pos):
                self.__setSloppyButton(receiver)
            else:
                if not receiver.isChecked():
                    index = [tab.button for tab in self.__tabs].index(receiver)
                    self.setCurrentIndex(index)
                #also update sloppy region if mouse is moved on the same icon
                self.__sloppyRegion = self.__calcSloppyRegion(pos)

        return QWidget.eventFilter(self, receiver, event)

    def leaveEvent(self, event):
        self.__sloppyButton = None
        self.__sloppyRegion = QRegion()

        return QWidget.leaveEvent(self, event)

class PagedMenu(QWidget):
    """
    Tabbed container for :class:`MenuPage` instances.
    """
    triggered = Signal(QAction)
    hovered = Signal(QAction)

    currentChanged = Signal(int)

    def __init__(self, parent=None, **kwargs):
        QWidget.__init__(self, parent, **kwargs)

        self.__pages = []
        self.__currentIndex = -1

        layout = QHBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(6)

        self.__tab = TabBarWidget(self)
        self.__tab.currentChanged.connect(self.setCurrentIndex)
        self.__tab.setChangeOnHover(True)

        self.__stack = MenuStackWidget(self)

        layout.addWidget(self.__tab, alignment=Qt.AlignTop)
        layout.addWidget(self.__stack)

        self.setLayout(layout)

    def addPage(self, page, title, icon=None, toolTip=None):
        """
        Add a `page` to the menu and return its index.
        """
        return self.insertPage(self.count(), page, title, icon, toolTip)

    def insertPage(self, index, page, title, icon=None, toolTip=None):
        """
        Insert `page` at `index`.
        """
        page.triggered.connect(self.triggered)
        page.hovered.connect(self.hovered)

        self.__stack.insertWidget(index, page)
        self.__tab.insertTab(index, title, icon, toolTip)
        return index

    def page(self, index):
        """
        Return the page at index.
        """
        return self.__stack.widget(index)

    def removePage(self, index):
        """
        Remove the page at `index`.
        """
        page = self.__stack.widget(index)
        page.triggered.disconnect(self.triggered)
        page.hovered.disconnect(self.hovered)

        self.__stack.removeWidget(page)
        self.__tab.removeTab(index)

    def count(self):
        """
        Return the number of pages.
        """
        return self.__stack.count()

    def setCurrentIndex(self, index):
        """
        Set the current page index.
        """
        if self.__currentIndex != index:
            self.__currentIndex = index
            self.__tab.setCurrentIndex(index)
            self.__stack.setCurrentIndex(index)
            self.currentChanged.emit(index)

    def currentIndex(self):
        """
        Return the index of the current page.
        """
        return self.__currentIndex

    def setCurrentPage(self, page):
        """
        Set `page` to be the current shown page.
        """
        index = self.__stack.indexOf(page)
        self.setCurrentIndex(index)

    def currentPage(self):
        """
        Return the current page.
        """
        return self.__stack.currentWidget()

    def indexOf(self, page):
        """
        Return the index of `page`.
        """
        return self.__stack.indexOf(page)

    def tabButton(self, index):
        """
        Return the tab button instance for index.
        """
        return self.__tab.button(index)

def qvariant_to_qbrush(variant):
    if qtcompat.HAS_QVARIANT:
        variant = variant.toPyObject()

    if isinstance(variant, QBrush):
        return variant
    else:
        return None

TAB_BUTTON_STYLE_TEMPLATE = """\
TabButton {
    qproperty-flat_: false;
    background: %s;
    border: none;
    border-bottom: 1px solid palette(mid);
    border-right: 1px solid palette(mid);
}

TabButton:checked {
    background: %s;
    border: none;
    border-top: 1px solid #609ED7;
    border-bottom: 1px solid #609ED7;
    border-right: 1px solid #609ED7;
}
"""

# TODO: Cleanup the QuickMenu interface. It should not have a 'dual' public
# interface (i.e. as an item model view (`setModel` method) and `addPage`,
# ...)

class QuickMenu(FramelessWindow):
    """
    A quick menu popup for the widgets.

    The widgets are set using :func:`QuickMenu.setModel` which must be a
    model as returned by :func:`QtWidgetRegistry.model`

    """

    #: An action has been triggered in the menu.
    triggered = Signal(QAction)

    #: An action has been hovered in the menu
    hovered = Signal(QAction)

    def __init__(self, parent=None, **kwargs):
        FramelessWindow.__init__(self, parent, **kwargs)
        self.setWindowFlags(Qt.Popup)

        self.__filterFunc = None

        self.__setupUi()

        self.__loop = None
        self.__model = None
        self.setModel(QStandardItemModel())
        self.__triggeredAction = None

    def __setupUi(self):
        self.setLayout(QVBoxLayout(self))
        self.layout().setContentsMargins(6, 6, 6, 6)

        self.__search = SearchWidget(self, objectName="search-line")

        self.__search.setPlaceholderText(
            self.tr("Search for widget or select from the list.")
        )

        self.layout().addWidget(self.__search)

        self.__frame = QFrame(self, objectName="menu-frame")
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.setSpacing(2)
        self.__frame.setLayout(layout)

        self.layout().addWidget(self.__frame)

        self.__pages = PagedMenu(self, objectName="paged-menu")
        self.__pages.currentChanged.connect(self.setCurrentIndex)
        self.__pages.triggered.connect(self.triggered)
        self.__pages.hovered.connect(self.hovered)

        self.__frame.layout().addWidget(self.__pages)

        self.setSizePolicy(QSizePolicy.Fixed,
                           QSizePolicy.Expanding)

        self.__suggestPage = SuggestMenuPage(self, objectName="suggest-page")
        self.__suggestPage.setActionRole(QtWidgetRegistry.WIDGET_ACTION_ROLE)
        self.__suggestPage.setIcon(icon_loader().get("icons/Search.svg"))

        if sys.platform == "darwin":
            view = self.__suggestPage.view()
            view.verticalScrollBar().setAttribute(Qt.WA_MacMiniSize, True)
            # Don't show the focus frame because it expands into the tab bar.
            view.setAttribute(Qt.WA_MacShowFocusRect, False)

        i = self.addPage(self.tr("Quick Search"), self.__suggestPage)
        button = self.__pages.tabButton(i)
        button.setObjectName("search-tab-button")
        button.setStyleSheet(
            "TabButton {\n"
            "    qproperty-flat_: false;\n"
            "    border: none;"
            "}\n")

        self.__search.textEdited.connect(self.__on_textEdited)

        self.__navigator = ItemViewKeyNavigator(self)
        self.__navigator.setView(self.__suggestPage.view())
        self.__search.installEventFilter(self.__navigator)

        self.__grip = WindowSizeGrip(self)
        self.__grip.raise_()

    def setSizeGripEnabled(self, enabled):
        """
        Enable the resizing of the menu with a size grip in a bottom
        right corner (enabled by default).

        """
        if bool(enabled) != bool(self.__grip):
            if self.__grip:
                self.__grip.deleteLater()
                self.__grip = None
            else:
                self.__grip = WindowSizeGrip(self)
                self.__grip.raise_()

    def sizeGripEnabled(self):
        """
        Is the size grip enabled.
        """
        return bool(self.__grip)

    def addPage(self, name, page):
        """
        Add the `page` (:class:`MenuPage`) with `name` and return it's index.
        The `page.icon()` will be used as the icon in the tab bar.

        """
        return self.insertPage(self.__pages.count(), name, page)

    def insertPage(self, index, name, page):
        icon = page.icon()

        tip = name
        if page.toolTip():
            tip = page.toolTip()

        index = self.__pages.insertPage(index, page, name, icon, tip)

        # Route the page's signals
        page.triggered.connect(self.__onTriggered)
        page.hovered.connect(self.hovered)

        # Install event filter to intercept key presses.
        page.view().installEventFilter(self)

        return index

    def createPage(self, index):
        """
        Create a new page based on the contents of an index
        (:class:`QModeIndex`) item.

        """
        page = MenuPage(self)

        page.setModel(index.model())
        page.setRootIndex(index)

        view = page.view()

        if sys.platform == "darwin":
            view.verticalScrollBar().setAttribute(Qt.WA_MacMiniSize, True)
            # Don't show the focus frame because it expands into the tab
            # bar at the top.
            view.setAttribute(Qt.WA_MacShowFocusRect, False)

        name = unicode(index.data(Qt.DisplayRole))
        page.setTitle(name)

        icon = index.data(Qt.DecorationRole).toPyObject()
        if isinstance(icon, QIcon):
            page.setIcon(icon)

        page.setToolTip(index.data(Qt.ToolTipRole).toPyObject())
        return page

    def __clear(self):
        for i in range(self.__pages.count() - 1, 0, -1):
            self.__pages.removePage(i)

    def setModel(self, model):
        """
        Set the model containing the actions.
        """
        if self.__model is not None:
            self.__model.dataChanged.disconnect(self.__on_dataChanged)
            self.__model.rowsInserted.disconnect(self.__on_rowsInserted)
            self.__model.rowsRemoved.disconnect(self.__on_rowsRemoved)
            self.__clear()

        for i in range(model.rowCount()):
            index = model.index(i, 0)
            self.__insertPage(i + 1, index)

        self.__model = model
        self.__suggestPage.setModel(model)
        if model is not None:
            self.__model.dataChanged.connect(self.__on_dataChanged)
            self.__model.rowsInserted.connect(self.__on_rowsInserted)
            self.__model.rowsRemoved.connect(self.__on_rowsRemoved)

    def __on_dataChanged(self, topLeft, bottomRight):
        parent = topLeft.parent()
        # Only handle top level item (categories).
        if not parent.isValid():
            for row in range(topLeft.row(), bottomRight.row() + 1):
                index = topLeft.sibling(row, 0, parent)
                # Note: the tab buttons are offest by 1 (to accommodate
                # the Suggest Page).
                button = self.__pages.tabButton(row + 1)
                brush = index.data(QtWidgetRegistry.BACKGROUND_ROLE)
                brush = qvariant_to_qbrush(brush)
                if brush is not None:
                    base_color = brush.color()
                    button.setStyleSheet(
                        TAB_BUTTON_STYLE_TEMPLATE %
                        (create_css_gradient(base_color),
                         create_css_gradient(base_color.darker(120)))
                    )

    def __on_rowsInserted(self, parent, start, end):
        # Only handle top level item (categories).
        if not parent.isValid():
            for row in range(start, end + 1):
                index = self.__model.index(row, 0)
                self.__insertPage(row + 1, index)

    def __on_rowsRemoved(self, parent, start, end):
        # Only handle top level item (categories).
        if not parent.isValid():
            for row in range(end, start - 1, -1):
                self.__removePage(row + 1)

    def __insertPage(self, row, index):
        page = self.createPage(index)
        page.setActionRole(QtWidgetRegistry.WIDGET_ACTION_ROLE)

        i = self.insertPage(row, page.title(), page)

        brush = index.data(QtWidgetRegistry.BACKGROUND_ROLE)
        brush = qvariant_to_qbrush(brush)
        if brush is not None:
            base_color = brush.color()
            button = self.__pages.tabButton(i)
            button.setStyleSheet(
                TAB_BUTTON_STYLE_TEMPLATE %
                (create_css_gradient(base_color),
                 create_css_gradient(base_color.darker(120)))
            )

    def __removePage(self, row):
        page = self.__pages.page(row)
        page.triggered.disconnect(self.__onTriggered)
        page.hovered.disconnect(self.hovered)
        page.view().removeEventFilter(self)
        self.__pages.removePage(row)

    def setFilterFunc(self, func):
        """
        Set a filter function.
        """
        if func != self.__filterFunc:
            self.__filterFunc = func
            for i in range(0, self.__pages.count()):
                self.__pages.page(i).setFilterFunc(func)

    def popup(self, pos=None, searchText=""):
        """
        Popup the menu at `pos` (in screen coordinates). 'Search' text field
        is initialized with `searchText` if provided.

        """
        if pos is None:
            pos = QPoint()

        self.__clearCurrentItems()

        self.__search.setText(searchText)
        self.__suggestPage.setFilterFixedString(searchText)

        self.ensurePolished()

        if self.testAttribute(Qt.WA_Resized) and self.sizeGripEnabled():
            size = self.size()
        else:
            size = self.sizeHint()

        desktop = QApplication.desktop()
        screen_geom = desktop.availableGeometry(pos)

        # Adjust the size to fit inside the screen.
        if size.height() > screen_geom.height():
            size.setHeight(screen_geom.height())
        if size.width() > screen_geom.width():
            size.setWidth(screen_geom.width())

        geom = QRect(pos, size)

        if geom.top() < screen_geom.top():
            geom.setTop(screen_geom.top())

        if geom.left() < screen_geom.left():
            geom.setLeft(screen_geom.left())

        bottom_margin = screen_geom.bottom() - geom.bottom()
        right_margin = screen_geom.right() - geom.right()
        if bottom_margin < 0:
            # Falls over the bottom of the screen, move it up.
            geom.translate(0, bottom_margin)

        # TODO: right to left locale
        if right_margin < 0:
            # Falls over the right screen edge, move the menu to the
            # other side of pos.
            geom.translate(-size.width(), 0)

        self.setGeometry(geom)

        self.show()

        if searchText:
            self.setFocusProxy(self.__search)
        else:
            self.setFocusProxy(None)

    def exec_(self, pos=None, searchText=""):
        """
        Execute the menu at position `pos` (in global screen coordinates).
        Return the triggered :class:`QAction` or `None` if no action was
        triggered. 'Search' text field is initialized with `searchText` if
        provided.

        """
        self.popup(pos, searchText)
        self.setFocus(Qt.PopupFocusReason)

        self.__triggeredAction = None
        self.__loop = QEventLoop()
        self.__loop.exec_()
        self.__loop.deleteLater()
        self.__loop = None

        action = self.__triggeredAction
        self.__triggeredAction = None
        return action

    def hideEvent(self, event):
        """
        Reimplemented from :class:`QWidget`
        """
        FramelessWindow.hideEvent(self, event)
        if self.__loop:
            self.__loop.exit()

    def setCurrentPage(self, page):
        """
        Set the current shown page to `page`.
        """
        self.__pages.setCurrentPage(page)

    def setCurrentIndex(self, index):
        """
        Set the current page index.
        """
        self.__pages.setCurrentIndex(index)

    def __clearCurrentItems(self):
        """
        Clear any selected (or current) items in all the menus.
        """
        for i in range(self.__pages.count()):
            self.__pages.page(i).view().selectionModel().clear()

    def __onTriggered(self, action):
        """
        Re-emit the action from the page.
        """
        self.__triggeredAction = action

        # Hide and exit the event loop if necessary.
        self.hide()
        self.triggered.emit(action)

    def __on_textEdited(self, text):
        self.__suggestPage.setFilterFixedString(text)
        self.__pages.setCurrentPage(self.__suggestPage)

    def triggerSearch(self):
        """
        Trigger action search. This changes to current page to the
        'Suggest' page and sets the keyboard focus to the search line edit.

        """
        self.__pages.setCurrentPage(self.__suggestPage)
        self.__search.setFocus(Qt.ShortcutFocusReason)

        # Make sure that the first enabled item is set current.
        self.__suggestPage.ensureCurrent()

    def keyPressEvent(self, event):
        if event.text():
            # Ignore modifiers, ...
            self.__search.setFocus(Qt.ShortcutFocusReason)
            self.setCurrentIndex(0)
            self.__search.keyPressEvent(event)

        FramelessWindow.keyPressEvent(self, event)
        event.accept()

    def event(self, event):
        if event.type() == QEvent.ShortcutOverride:
            log.debug("Overriding shortcuts")
            event.accept()
            return True
        return FramelessWindow.event(self, event)

    def eventFilter(self, obj, event):
        if isinstance(obj, QTreeView):
            etype = event.type()
            if etype == QEvent.KeyPress:
                # ignore modifiers non printable characters, Enter, ...
                if event.text() and event.key() not in \
                        [Qt.Key_Enter, Qt.Key_Return]:
                    self.__search.setFocus(Qt.ShortcutFocusReason)
                    self.setCurrentIndex(0)
                    self.__search.keyPressEvent(event)
                    return True

        return FramelessWindow.eventFilter(self, obj, event)

class ItemViewKeyNavigator(QObject):
    """
    A event filter class listening to key press events and responding
    by moving 'currentItem` on a :class:`QListView`.

    """
    def __init__(self, parent=None):
        QObject.__init__(self, parent)
        self.__view = None

    def setView(self, view):
        """
        Set the QListView.
        """
        if self.__view != view:
            self.__view = view

    def view(self):
        """
        Return the view
        """
        return self.__view

    def eventFilter(self, obj, event):
        etype = event.type()
        if etype == QEvent.KeyPress:
            key = event.key()
            if key == Qt.Key_Down:
                self.moveCurrent(1, 0)
                return True
            elif key == Qt.Key_Up:
                self.moveCurrent(-1, 0)
                return True
            elif key == Qt.Key_Tab:
                self.moveCurrent(0, 1)
                return  True
            elif key == Qt.Key_Enter or key == Qt.Key_Return:
                self.activateCurrent()
                return True

        return QObject.eventFilter(self, obj, event)

    def moveCurrent(self, rows, columns=0):
        """
        Move the current index by rows, columns.
        """
        if self.__view is not None:
            view = self.__view
            model = view.model()

            curr = view.currentIndex()
            curr_row, curr_col = curr.row(), curr.column()

            sign = 1 if rows >= 0 else -1
            row = curr_row + rows

            row_count = model.rowCount()
            for i in range(row_count):
                index = model.index((row + sign * i) % row_count, 0)
                if index.flags() & Qt.ItemIsEnabled:
                    view.setCurrentIndex(index)
                    break
            # TODO: move by columns

    def activateCurrent(self):
        """
        Activate the current index.
        """
        if self.__view is not None:
            curr = self.__view.currentIndex()
            if curr.isValid():
                # TODO: Does this work? We are emitting signals that are
                # defined by a different class. This might break some things.
                # Should we just send the keyPress events to the view, and let
                # it handle them.
                self.__view.activated.emit(curr)

    def ensureCurrent(self):
        """
        Ensure the view has a current item if one is available.
        """
        if self.__view is not None:
            model = self.__view.model()
            curr = self.__view.currentIndex()
            if not curr.isValid():
                for i in range(model.rowCount()):
                    index = model.index(i, 0)
                    if index.flags() & Qt.ItemIsEnabled:
                        self.__view.setCurrentIndex(index)
                        break

class WindowSizeGrip(QSizeGrip):
    """
    Automatically positioning :class:`QSizeGrip`.
    The widget automatically maintains its position in the window
    corner during resize events.

    """
    def __init__(self, parent):
        QSizeGrip.__init__(self, parent)
        self.__corner = Qt.BottomRightCorner

        self.resize(self.sizeHint())

        self.__updatePos()

    def setCorner(self, corner):
        """
        Set the corner (:class:`Qt.Corner`) where the size grip should
        position itself.

        """
        if corner not in [Qt.TopLeftCorner, Qt.TopRightCorner,
                          Qt.BottomLeftCorner, Qt.BottomRightCorner]:
            raise ValueError("Qt.Corner flag expected")

        if self.__corner != corner:
            self.__corner = corner
            self.__updatePos()

    def corner(self):
        """
        Return the corner where the size grip is positioned.
        """
        return self.__corner

    def eventFilter(self, obj, event):
        if obj is self.window():
            if event.type() == QEvent.Resize:
                self.__updatePos()

        return QSizeGrip.eventFilter(self, obj, event)

    def showEvent(self, event):
        if self.window() != self.parent():
            log.error("%s: Can only show on a top level window.",
                      type(self).__name__)

        return QSizeGrip.showEvent(self, event)

    def __updatePos(self):
        window = self.window()

        if window is not self.parent():
            return

        corner = self.__corner
        size = self.sizeHint()

        window_geom = window.geometry()
        window_size = window_geom.size()

        if corner in [Qt.TopLeftCorner, Qt.BottomLeftCorner]:
            x = 0
        else:
            x = window_geom.width() - size.width()

        if corner in [Qt.TopLeftCorner, Qt.TopRightCorner]:
            y = 0
        else:
            y = window_size.height() - size.height()

        self.move(x, y)


from django.contrib import admin
from .models import User, AuctionBids, AuctionListing,AuctionListingComments,Watchlist
# Register your models here.

class AuctionAdminForm(admin.ModelAdmin):
    def save_model(self, request, obj, form, change):
        if not obj.creator:
            obj.creator = request.user  # Ustaw właściciela aukcji na aktualnie zalogowanego użytkownika
        super().save_model(request, obj, form, change)

admin.site.register(User)
admin.site.register(AuctionBids)
admin.site.register(AuctionListingComments)
admin.site.register(AuctionListing, AuctionAdminForm)
admin.site.register(Watchlist)


import numpy as np
import cv2
from pathlib import Path

videos_path = Path('~/src/BeholderPi/beholder/img/').expanduser().resolve()

ts = '2019-02-17_17-53-02'

images = []
for n, img_path in enumerate(sorted(videos_path.glob(ts + '_eye*.png'))):
    images.append(cv2.imread(str(img_path)))

stitcher = cv2.Stitcher_create(True)
(status, stitched) = stitcher.stitch(images)

stitched = np.rot90(cv2.flip(cv2.flip(stitched, 1), 0))

# if the status is '0', then OpenCV successfully performed image
# stitching
if status == 0:
    # write the output stitched image to disk
    cv2.imwrite('test.png', stitched)

    # display the output stitched image to our screen
    cv2.imshow("Stitched", stitched)
    cv2.waitKey(0)

# otherwise the stitching failed, likely due to not enough keypoints)
# being detected
else:
    print("[INFO] image stitching failed ({})".format(status))


import math
import os

import cntk as C
import numpy as np
import skimage.transform
from PIL import Image
from pathlib import Path

def load_image_as_hwc(path, output_shape, scale=None, rotation=None, shift=None, order=1, mode='edge', normalize=True):
    image = Image.open(path)

    if not len(image.getbands()) == 3:
        image = image.convert('RGB')

    source_width = image.size[0]
    source_height = image.size[1]
    target_width = output_shape[0]
    target_height = output_shape[1]

    if source_width == target_width and source_height == target_height and scale is None and rotation is None and shift is None:
        return np.asarray(image)

    source_ratio = source_width / source_height
    target_ratio = target_width / target_height

    if target_ratio < source_ratio:
        resize_scale = target_width / source_width
    else:
        resize_scale = target_height / source_height

    if scale:
        resize_scale *= scale

    image = image.resize(size=(int(source_width * resize_scale),
                               int(source_height * resize_scale)), resample=Image.LANCZOS)

    image_array = np.asarray(image, dtype=np.float64)  # HWC

    # centerize
    t = skimage.transform.AffineTransform(
        translation=(-image.size[0] * 0.5, -image.size[1] * 0.5))

    if rotation:
        radian = (rotation / 180.0) * math.pi
        t += skimage.transform.AffineTransform(rotation=radian)

    t += skimage.transform.AffineTransform(
        translation=(target_width * 0.5, target_height * 0.5))

    if shift:
        t += skimage.transform.AffineTransform(
            translation=(target_width * shift[0], target_height * shift[1]))

    warp_shape = (output_shape[1], output_shape[0])

    image_array = skimage.transform.warp(
        image_array, (t).inverse, output_shape=warp_shape, order=order, mode=mode)

    if normalize:
        image_array /= 255.0

    return image_array

def load_model_and_tags(project_path):
    if not os.path.exists(project_path):
        raise Exception(f'{project_path} is not exists.')

    tags_path = os.path.join(project_path, 'tags.txt')
    model_path = os.path.join(project_path, 'model.cntk')

    with open(tags_path, 'r') as tags_stream:
        tag_array = np.array([tag for tag in (tag.strip()
                                              for tag in tags_stream) if tag])

    model = C.load_model(model_path)

    return (model, tag_array)

def get_files_recursively(folder_path):
    allowed_patterns = [
        '*.[Pp][Nn][Gg]',
        '*.[Jj][Pp][Gg]',
        '*.[Jj][Pp][Ee][Gg]',
        '*.[Gg][Ii][Ff]'
    ]

    image_path_list = [
        str(path) for pattern in allowed_patterns for path in Path(folder_path).rglob(pattern)]

    return image_path_list


import json

import requests
from requests.auth import HTTPBasicAuth
from aiogram import types

from loader import bot
from data.config import env

TEST_REQUEST_URL = env.str('TEST_PAYMENT_URL')
PAYMENT_URL = env.str('PAYMENT_URL')

def get_payment_link(amount_rub: int, description: str, user_id: int, invoice_id: int, json_data: dict):
    data = {
        "Amount": amount_rub,
        "Currency": "RUB",
        "Description": description,
        "AccountId": str(user_id),
        "InvoiceId": invoice_id,
        "JsonData": json.dumps(json_data),
    }
    result = requests.post(
        PAYMENT_URL,
       params=data,
       auth=HTTPBasicAuth(env.str("PAYMENT_PUBLIC_ID"), env.str("PAYMENT_API_SECRET"))
    )
    result = result.json()
    if result.get("Success"):
        return result.get("Model").get("Url")
    return False

def get_invoice_data(chat_id: int, title: str, description: str, payload: str,
                     amount_rub: int) -> dict:
    return {
        "chat_id": chat_id,
        "title": title,
        "description": description,
        "payload": payload,
        "provider_token": env.str("YOOKASSA_TOKEN"),
        "currency": "RUB",
        "start_parameter": "test",
        "prices": [types.LabeledPrice(label="руб", amount=amount_rub)]
    }

async def send_invoice(chat_id: int, title: str, description: str, payload: str,
                       amount_rub: int):
    await bot.send_invoice(
        chat_id=chat_id,
        title=title,
        description=description,
        payload=payload,
        provider_token=env.str("YOOKASSA_TOKEN"),
        start_parameter="true",
        currency="RUB",
        prices=[types.LabeledPrice(label="руб", amount=amount_rub * 100)]
    )


# This puzzle works you through various functionalities of lists
# If you do all the steps correctly - you will be able to fix the google doc address and get the prize

# fix this google doc address - and find the prize
mangled = "https://docs.google.com/document/d/1FK7pUWYt139erPO48fIc0ukHkpSYUhkAwYWPwTMfn8owmxlR7OnkYgo/edit?usp=sharing"

# make an empty list called:  list1
list1 = []

# put the numbers 12 through 255 inclusive into list1  [ inclusive means  12,13,...,255  , including the 12 and 255 ]
for num in range(12, 256):
    list1.append(num)

# select every 13th entry of list1 , starting at and including the 11th entry - put the results into list2
list2 = list1[10:256:13]
print(list2)

# remove the last 8 entries of list2
del list2 [-8:]
print(list2)

# remove the first 2 entries of list2

# convert the string "YakAttacksAreANormalPartOfLife"   into a list of letters - call it list3

# change list3 to only keep every 4th entry from its original list3

# remove the first letter 'k'  from list3

# make a copy of list2 , call it list4

# set the 5th element of list4 to be 37

# create list5 which is  the elements of list2 followed by elements of list4 , in a single list

# append 19 to list5

# keep only each 7th entry in list5

# insert the 2nd element of list5  into location 2  of list3

# insert the 1st element of list5 into location 7 of list3

# now lets print out the characters in list3
"""for i in list3:
    print(i,end="")
print()"""

# the string you just printed  will have some letters and numbers
# look for that string the mangled address above - and delete it.
# what is left will be an address for a google doc - go there and leave your mark


import sys
import collections
input = sys.stdin.readline

if __name__ == "__main__":
    answer = 0
    R, C = map(int, input().split(" "))
    word_list = collections.deque(list(collections.deque([]) for i in range(C)))
    first_word = input().rstrip()
    for i in range(1, R) :
        tmep = input().rstrip()
        for j in range(C) :
            word_list[j].append(tmep[j])

    for i in range(C -1) :
        w_sets = set()
        j = 0
        for words in word_list :
            w_sets.add("".join(words))
            word_list[j].popleft()
            j += 1
        if len(w_sets) == C :
            answer += 1
        else :
            break
    print(answer)


tri = []

while True:
    tri = list(map(int,input().split()))
    if sum(tri) == 0:
        break
    elif max(tri) >= sum(tri) - max(tri):
        print("Invalid")
    else:
        if len(set(tri)) == 1:
            print("Equilateral")
        elif len(set(tri)) == 2:
            print("Isosceles")
        elif len(set(tri)) == 3:
            print("Scalene")

백준/Bronze/5073. 삼각형과 세 변/삼각형과 세 변.py


from sklearn.base import BaseEstimator, ClassifierMixin
from scipy.special import softmax
from sklearn.preprocessing import OneHotEncoder
import time
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import load_iris
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
from sklearn.preprocessing import StandardScaler
from scipy.special import softmax
from sklearn.metrics import plot_confusion_matrix

class MyLogisticReg(BaseEstimator, ClassifierMixin):
    def __init__(self):
        self.params = {}
        self.n_features = None
        self.n_class = None
        self.encoder = OneHotEncoder(sparse=False)
        self.verbose = 0

    def init_params(self, X, y):
        """Randomly initializing parameters"""
        self.n_features = X.shape[1]  # number of features
        # the matrix for slope coefficients
        self.params['coef'] = np.random.randn(self.n_features, self.n_class)
        # the y-intercept
        self.params['intercept'] = np.random.randn(1, self.n_class)
        if self.verbose:
            print("[INFO] Initialized parameters.")
            print(f"Shape of coefficient matrix: {self.params['coef'].shape}")
            print(f"Shape of intercept matrix: {self.params['intercept'].shape}")

    def get_logits(self, X, y=None):
        # logits = log(odds) = X@W + b
        if 'coef' not in self.params and y is None:
            # initialize the parameters if haven't
            # self.init_params(X)
            raise Exception("This LogisticRegression instance is not fitted yet." +
                             "Call 'fit' with appropriate arguments before using this estimator.")
        elif 'coef' not in self.params and y is not None:
            print("[INFO] The model is not fitted yet. Using random parameters.")
            self.init_params(X, y)
        return X @ self.params['coef'] + self.params['intercept']

    def predict_proba(self, X, y=None):
        """
        If binary classification, use sigmoid function.
        If multi-class classification, use softmax function.
        """
        if y is not None:
            # predict using randomly initialized parameters
            logits = self.get_logits(X, y)
        else:
            logits = self.get_logits(X)

        if self.n_class == 1:
            # binary classification uses sigmoid function
            return 1 / (1 + np.exp(-logits))

        # https://machinelearningmastery.com/softmax-activation-function-with-python/
        # https://stackoverflow.com/questions/43290138/softmax-function-of-a-numpy-array-by-row
        # or just scipy.special.softmax
        # minus by the max values in every row to prevent exponentials from reaching infinity
        mx = np.max(logits, axis=-1, keepdims=True)
        numerator = np.exp(logits - mx)
        denominator = np.sum(numerator, axis=-1, keepdims=True)
        return numerator / denominator

    def fit(self, X, y, learning_rate=0.05, iterations=1000, verbose=0):
        self.verbose = verbose

        if isinstance(X, pd.DataFrame):
            # convert Dataframe to numpy for faster computations
            X = X.values
        if isinstance(y, pd.DataFrame):
            y = y.values

        # get the number of classes in the target labels
        n_classes = len(np.unique(y.flatten()))
        # set to 1 instead of 2 for binary classification,
        #  because of how matrix multiplication works for binary classifcation
        self.n_class = n_classes if n_classes > 2 else 1
        if self.n_class > 2:
            # one-hot encode the target labels for multi-class classification
            y = self.encoder.fit_transform(y)

        # initialize parameters
        self.init_params(X, y)
        m = X.shape[0]  # number of samples

        if self.verbose:
            print("[INFO] Training ...")
        for i in range(1, iterations + 1):
            # using method for binary classification
            if self.n_class == 1:
                # make predictions by computing probability
                y_proba = self.predict_proba(X)
                # calculate the binary cross-entropy loss
                loss = - (1 / m) * np.sum(y * np.log(y_proba) \
                            + (1 - y) * np.log(1 - y_proba))

                # calculate gradients via derivatives
                #  with respect to loss function (refer above)
                dW = (1 / m) * (X.T @ (y_proba - y))
                db = (1 / m) * np.sum(y_proba - y)

            # using method for multi-class classification
            else:
                # make predictions by computing probability
                y_proba = self.predict_proba(X, y)
                # calculate the categorical cross-entropy loss.
                # Here the `y` stands for "target" (the true class labels),
                #  and the `y_proba` stands for output
                #  (the computed probability via softmax;
                #  not the predicted class label).
                loss = - (1 / m) * np.sum(y * np.log(y_proba))

                # calculate gradients via derivatives
                #  with respect to loss function (refer above)
                dW = (1 / m) * (X.T @ (y_proba - y))
                db = (1 / m) * np.sum((y_proba - y), axis=0, keepdims=True)
                # print(dW.shape, db.shape)
                # break

            # use gradient descent to update parameters
            # parameter = parameter - (learning_rate * derivative_of_parameter)
            self.params['coef'] -= (learning_rate * dW)
            self.params['intercept'] -= (learning_rate * db)

            if self.verbose and (i == 1 or i % 100 == 0):
                print(f"\nIteration {i}/{iterations}")
                print("--" * 12)
                print(f"Loss: {loss}")
                print(f"Coefficient:\n{self.params['coef']}")
                print(f"Intercept:\n{self.params['intercept']}")

    def predict(self, X, threshold=0.5):
        y_proba = self.predict_proba(X)
        if self.n_class == 1:
            y_pred = np.where(y_proba > threshold, 1, 0)
        else:
            # get the index of the max probability as the predicted class
            y_pred = np.argmax(y_proba, axis=1)
        return y_pred

    def predict_score(self, X, y):
        from sklearn.metrics import accuracy_score
        y_pred = self.predict(X)
        # if self.n_class > 1:
        #     # one-hot encode it first before
        #     y = self.encoder.transform(y)
        #     y = np.argmax(y, axis=1)
        return accuracy_score(y.reshape(-1), y_pred.reshape(-1))
# from scipy.special import softmax
# def softmax(x):
#     # https://stackoverflow.com/questions/43290138/softmax-function-of-a-numpy-array-by-row
#     # minus by the max values in every row to prevent exponentials from reaching infinity
#     mx = np.max(x, axis=-1, keepdims=True)
#     print(f"{mx = }")
#     print(f"{x - mx = }")
#     numerator = np.exp(x - mx)
#     denominator = np.sum(numerator, axis=-1, keepdims=True)
#     print(f"{denominator = }")
#     return numerator / denominator

# MULTI-CLASS CLASSIFICATION

X2, y2 = load_iris(return_X_y=True)
y2 = y2.reshape(-1, 1)
print(f"{X2.shape}\n{y2.shape}")
my_log_reg = MyLogisticReg()
# set verbose=1 to see the training progress
my_log_reg.fit(X2, y2, learning_rate=0.05, iterations=1500, verbose=0)
X_train2, X_test2, y_train2, y_test2 = train_test_split(X2, y2, test_size=0.3, random_state=42)
log_reg = LogisticRegression(penalty='none', solver='sag', multi_class='multinomial', random_state=42)
log_reg.fit(X2, y2)
print("Result in accuracy:")
print(f"My implementation\t: {my_log_reg.predict_score(X2, y2)}")
print(f"Sklearn implementation\t: {log_reg.score(X2, y2)}")

plot_confusion_matrix(my_log_reg, X2, y2, cmap='Blues', display_labels=['Healthy', 'Heart Disease'])
plt.grid(None)


#function to find difference of maximum and minimum value
def find_diff(l):
    a=max(l)
    b=min(l)
    return a-b

# to get all combinations
def combination(lst,n):
    if(n==0):
        return [[]]
    l=[]
    for i in range(0,len(lst)):
        m=lst[i]
        remlst=lst[i+1:]
        for p in combination(remlst,n-1):
            l.append([m]+p)
    return l

#open input file
fi = open(r"F:\highpeak\input.txt", "r")

#open output file
fo = open(r"F:\highpeak\answer.txt","w")

#read contents of the file
temp = fi.read().splitlines()

pricelist=[]
d={}
index=0

#find number of employees
nemp=int(temp[0][-1])

for i in range(4,14): # to get all goodies and prices mapped to a dictionary
    m=temp[i].split(":")
    d[int(m[1])]=m[0]
    pricelist.append(int(m[1]))

#get all combinations of the pricelist
combo=combination(pricelist,nemp)

minval=find_diff(combo[0])

for i in range(1,len(combo)):
    val=find_diff(combo[i])
    if(minval>=val):
        minval=val      # minimum difference
        index=i         # index of combination with minval

#write output to file
fo.write("The goodies selected for distribution are:\n \n")
for i in combo[index]:
    s=d[i]+": "+str(i)
    fo.write(s+"\n")

fo.write("\nAnd the difference between the chosen goodie with highest price and the lowest price is "+str(minval))

#close files
fo.close()
fi.close()


from pyradioconfig.parts.ocelot.calculators.calc_demodulator import CALC_Demodulator_ocelot
from math import *

class CALC_Demodulator_Margay(CALC_Demodulator_ocelot):

    # override inhereited variable defining threshold for _channel_filter_clocks_valid
    chf_required_clks_per_sample = 3  # fixed in PGMARGAY-1557

    def _check_trecs_required_clk_cycles(self, adc_freq, baudrate, osr, dec0, dec1, xtal_frequency_hz, relaxsrc2,
                                         model):
        # Returns True if the filter chain configuration meets the requirement for trecs
        # minimum clock cycles between samples. Returns False if the configuration is invalid
        #
        # PGMARGAY-106 added configurable clk spacing for interpolated samples. Compute minimum sample clk spacing.
        # (same as sol) but since margay inherits from ocelot, copy here.

        # calculate the src_ratio as this function is called in the process of evaluating
        # osr, dec0, dec1, so the src_ratio_actual cannot be calculated
        dec1_freq = adc_freq / (8 * dec0 * dec1)
        src_freq = baudrate * osr
        src_ratio = src_freq / dec1_freq
        TRECS_REQUIRED_CLKS_PER_SAMPLE = 3

        if src_ratio > 1:
            # sample clk is not equi-spaced, interpolated samples have best-case spacing of 1/2 relative to src disabled
            # while using UPGAPS clk delay adjustment in the SRW CMU
            cycles_per_sample = floor(xtal_frequency_hz / src_freq / 2)
        else:
            cycles_per_sample = floor(xtal_frequency_hz / src_freq)

        if cycles_per_sample < TRECS_REQUIRED_CLKS_PER_SAMPLE:
            return False
        else:
            return True


from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
from pydantic import HttpUrl
from starlette.requests import Request
from tweepy import API

from app.api.deps import get_backend, get_twitter_api
from app.core import TwitterSentiment
from app.decorator import async_log_response
from app.feature_store.backends import Backend
from app.feature_store.core import Keys, get_cache, set_cache
from app.schemas import SentimentResponse

router = APIRouter()

@router.get("/inference", response_model=SentimentResponse)
@async_log_response
async def inference(
    request: Request,
    tweetUrl: HttpUrl,
    background_tasks: BackgroundTasks,
    feature_store: Backend = Depends(get_backend),
    twitter_api: API = Depends(get_twitter_api),
):
    try:
        tweet = twitter_api.get_status(tweetUrl.split("/")[-1])
    except Exception as e:
        raise HTTPException(status_code=400, detail=f"Error{type(e).__name__}: {e}")
    key = Keys(tweet=tweet)
    data = await get_cache(keys=key, feature_store=feature_store)
    if not data:
        request.app.logger.info("Prediction is not exist in feature store")
        twitter = TwitterSentiment(**request.app.model_params)

        prediction = twitter.prediction(tweet.text)
        if prediction:
            result = SentimentResponse(
                sentiment_analyst=prediction, text_input=tweet.text
            )
            background_tasks.add_task(set_cache, result.dict(), key, feature_store)
        else:
            raise HTTPException(status_code=400, detail="Empty prediction")
    else:
        request.app.logger.info("Prediction hits")
        result = SentimentResponse(**data)

    return result

@router.get("/clear")
async def refresh(prefix: str, feature_store: Backend = Depends(get_backend)):
    flag_clear = await feature_store.clear(namespace=prefix)
    if flag_clear and any(flag_clear):
        return "success"
    return "fail"


from files.file import *
import zlib

class ZLIBFile(File):
    """ZLIB File Class - File class for reading compressed zlib files (ex. Satisfactory save files)."""

    def __init__(self, save_file, perms='rb') -> None:
        super().__init__(save_file, perms)
        self.header = {}

    def get_header(self, output_file) -> object:
        """Processes ZLIB File - returns header and saves zlib data"""
        self.process_all(output_file)

        return self.header

    def process_all(self, output_file) -> None:
        """Processes header and zlib data."""
        if self._is_closed:
            print('File is closed - Unable to process :(')
            return
        self._process_header()
        self._process_zlib_data(output_file)
        self.close()

    def _process_header(self) -> None:
        """Process the header data from Satisfactory save file."""
        self.header = {
            'headerVersion': self.read_int(),
            'saveVersion': self.read_int(),
            'buildVersion': self.read_int(),
            'worldType': self.read_str(),
            'worldProps': self.read_str(),
            'sessionName': self.read_str(),
            'playTimeSeconds': self.read_int(),
            'saveDateInTicks': self.read_long(),
            'sessionVisibility': self.read_byte(),
            'editorObjectVersion': self.read_int(),
            'modMetadata': self.read_str(),
            'modFlags': self.read_int(),
            'objects': [],
            'collected': []
        }

    def _process_zlib_data(self, output_file) -> None:
        """Processes binary file for zlib data."""
        with open(output_file, 'wb') as out_file:
            pass

        while self.file.tell() < self.file_size:
            chunk_header = [self.read_long() for _ in range(6)]

            self._log_chunks(chunk_header)

            chunk = self.read_bytes(chunk_header[2])
            decompressed_chunk = zlib.decompress(chunk)

            with open(output_file, 'ab') as out_file:
                out_file.write(decompressed_chunk)

    def _log_chunks(self, chunk_header) -> None:
        """Log data from decompressing zlib chunks."""
        print(f'PACKAGE_FILE_TAG: {chunk_header[0]}')
        print(f'Maximum Chunk Size: {chunk_header[1]}')
        print(f'Current Chunk Compressed Length: {chunk_header[2]}')    # also chunk_header[4]
        print(f'Maximum Chunk Uncompressed Length: {chunk_header[3]}')  # also chunk_header[5]


import gc
import os

def rom_space():
    f_bsize, f_frsize, f_blocks, f_bfree, f_bavail, _, _, _, _, _ = os.statvfs('//')
    print('ROM: {0} Total {1} ({2:.2f}%) Free'.format(f_frsize * f_blocks, f_bsize * f_bfree,
                                                      ((f_bsize * f_bfree) / (f_frsize * f_blocks)) * 100))

def ram_space():
    free_ram = gc.mem_free()
    available_ram = gc.mem_alloc()
    total_ram = free_ram + available_ram
    percent_free = '{0:.2f}%'.format(free_ram / total_ram * 100)
    print('RAM: {0} Total {1} ({2}) Free'.format(total_ram, free_ram, percent_free))


"""
Задача 5.3
Создать текстовый файл (не программно),
построчно записать фамилии сотрудников и величину их окладов (не менее 10 строк).
Определить, кто из сотрудников имеет оклад менее 20 тыс.,
вывести фамилии этих сотрудников.
Выполнить подсчет средней величины дохода сотрудников.

Пример файла:
Иванов 23543.12
Петров 13749.32
"""

my_file = 'Task_5.3.txt'

def list_employees(data_file):
    lines = [line for line in data_file]
    employees = []
    for line_employee in lines:
        arr = line_employee.split()
        employee = {"name": arr[0], "cash": float(arr[1])}
        employees.append(employee)
        if employee["cash"] < 20000:
            print(f'{employee["name"]} {employee["cash"]}, получает меньше 20 000 руб')
    return employees

def average_income_f(employees):
    average_income = 0
    number_employees = 0
    for income in employees:
        number_employees += 1
        average_income += income["cash"]
    average_income = average_income / number_employees
    print(f'Средняя доходность сотрудников: {round(average_income, 2)}')

with open(my_file, 'r', encoding='utf-8') as data:
    average_income_f(list_employees(data))


from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import os
import numpy as np

from tensor2tensor.data_generators import generator_utils
from tensor2tensor.data_generators import problem
from tensor2tensor.data_generators import video_utils
from tensor2tensor.layers import modalities
from tensor2tensor.utils import contrib
from tensor2tensor.utils import registry

import tensorflow.compat.v1 as tf
import tensorflow_datasets as tfds
from tensorflow_datasets.video import moving_sequence

DATA_URL = (
    "http://www.cs.toronto.edu/~nitish/unsupervised_video/mnist_test_seq.npy")
SPLIT_TO_SIZE = {
    problem.DatasetSplit.TRAIN: 100000,
    problem.DatasetSplit.EVAL: 10000,
    problem.DatasetSplit.TEST: 10000}

@registry.register_problem
class VideoMovingMnist(video_utils.VideoProblem):
  """MovingMnist Dataset."""

  @property
  def num_channels(self):
    return 1

  @property
  def frame_height(self):
    return 64

  @property
  def frame_width(self):
    return 64

  @property
  def is_generate_per_split(self):
    return True

  # num_videos * num_frames
  @property
  def total_number_of_frames(self):
    return 100000 * 20

  def max_frames_per_video(self, hparams):
    return 20

  @property
  def random_skip(self):
    return False

  @property
  def dataset_splits(self):
    """Splits of data to produce and number of output shards for each."""
    return [
        {"split": problem.DatasetSplit.TRAIN, "shards": 10},
        {"split": problem.DatasetSplit.EVAL, "shards": 1},
        {"split": problem.DatasetSplit.TEST, "shards": 1}]

  @property
  def extra_reading_spec(self):
    """Additional data fields to store on disk and their decoders."""
    data_fields = {
        "frame_number": tf.FixedLenFeature([1], tf.int64),
    }
    decoders = {
        "frame_number":
            contrib.slim().tfexample_decoder.Tensor(tensor_key="frame_number"),
    }
    return data_fields, decoders

  def hparams(self, defaults, unused_model_hparams):
    p = defaults
    p.modality = {"inputs": modalities.ModalityType.VIDEO,
                  "targets": modalities.ModalityType.VIDEO}
    p.vocab_size = {"inputs": 256,
                    "targets": 256}

  def get_test_iterator(self, tmp_dir):
    path = generator_utils.maybe_download(
        tmp_dir, os.path.basename(DATA_URL), DATA_URL)
    with tf.io.gfile.GFile(path, "rb") as fp:
      mnist_test = np.load(fp)
    mnist_test = np.transpose(mnist_test, (1, 0, 2, 3))
    mnist_test = np.expand_dims(mnist_test, axis=-1)
    mnist_test = tf.data.Dataset.from_tensor_slices(mnist_test)
    return mnist_test.make_initializable_iterator()

  def map_fn(self, image, label):
    sequence = moving_sequence.image_as_moving_sequence(
        image, sequence_length=20)
    return sequence.image_sequence

  def get_train_iterator(self):
    mnist_ds = tfds.load("mnist:3.*.*", split=tfds.Split.TRAIN,
                         as_supervised=True)
    mnist_ds = mnist_ds.repeat()
    moving_mnist_ds = mnist_ds.map(self.map_fn).batch(2)
    moving_mnist_ds = moving_mnist_ds.map(lambda x: tf.reduce_max(x, axis=0))
    return moving_mnist_ds.make_initializable_iterator()

  def generate_samples(self, data_dir, tmp_dir, dataset_split):
    with tf.Graph().as_default():
      # train and eval set are generated on-the-fly.
      # test set is the official test-set.
      if dataset_split == problem.DatasetSplit.TEST:
        moving_ds = self.get_test_iterator(tmp_dir)
      else:
        moving_ds = self.get_train_iterator()

      next_video = moving_ds.get_next()
      with tf.Session() as sess:
        sess.run(moving_ds.initializer)

        n_samples = SPLIT_TO_SIZE[dataset_split]
        for _ in range(n_samples):
          next_video_np = sess.run(next_video)
          for frame_number, frame in enumerate(next_video_np):
            yield {
                "frame_number": [frame_number],
                "frame": frame,
            }


# 2 вариант
# Если это дз каким-то образом должно быть сделано с помощью шифра, который проходили на последней паре (на которой я не была), я совершенно не понимаю, как его применить здесь.
# Если необходимо, я попытаюсь узнать больше и переделать.
word = input('Введите слово: ')
for i in range(len(word)):
    if i == 0:
        print (word[i])
    else:
        print (word[:i+1])

дз старое9.py