import os
from PyQt6.QtWidgets import (
    QWidget,
    QGroupBox,
    QLabel,
    QVBoxLayout,
    QPushButton,
    QFileDialog,
    QHBoxLayout,
    QTextEdit,
    QDialog,
    QLineEdit,
)
from typing import Any, Optional
from util import ExtendedEpubBook

class QGrowingTextEdit(QTextEdit):
    def __init__(self, parent=None):
        super().__init__(parent=parent)
        self.document().contentsChanged.connect(self.sizeChange)
        self.document().documentLayout().documentSizeChanged.connect(self.sizeChange)

        self.heightMin = 0
        self.heightMax = 65000

    def sizeChange(self):
        docHeight: int = int(self.document().size().height())
        if self.heightMin <= docHeight <= self.heightMax:
            self.setMinimumHeight(docHeight + 10)

    def setText(self, text):
        super().setText(text)

class EpubFileDialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setWindowTitle("ファイル名")
        layout1 = QVBoxLayout()
        layout2 = QHBoxLayout()
        layout3 = QHBoxLayout()

        self.label = QLabel(".epub", self)
        self.line_edit = QLineEdit(self)
        self.cancel_button = QPushButton("Cancel", self)
        self.ok_button = QPushButton("OK", self)

        layout2.addWidget(self.line_edit)
        layout2.addWidget(self.label)

        layout3.addWidget(self.cancel_button)
        layout3.addWidget(self.ok_button)

        layout1.addLayout(layout2)
        layout1.addLayout(layout3)

        self.setLayout(layout1)

        self.ok_button.clicked.connect(self.accept)
        self.cancel_button.clicked.connect(self.reject)

    def getText(self):
        return self.line_edit.text()

class TextNotFoundException(Exception):
    pass

class MetadataContents:
    win: QWidget
    id_name: str = ""
    main_layout: QVBoxLayout
    main_box_layout: QGroupBox
    disabled: bool = False

    def __init__(self):
        self.disabled = False
        return

    def add_footer(self):
        id_label = QLabel("id", None)
        self.id_text = QLineEdit()
        self.id_text.setText(self.id_name)
        self.delete_button = QPushButton("削除")
        self.delete_button.clicked.connect(self.remove)

        # layouts

        layout = QHBoxLayout()
        layout.addWidget(id_label, 1)
        layout.addWidget(self.id_text, 1)
        layout.addStretch(1)
        layout.addWidget(self.delete_button, 1)
        self.main_layout.addLayout(layout)

    def remove(self):
        self.disabled = True
        self.win.deleteLater()

    def setup_ui(self, win: QWidget) -> None:
        self.win = win
        self.main_box_layout = QGroupBox("Template")
        self.main_layout = QVBoxLayout()
        self.main_box_layout.setLayout(self.main_layout)

    def set_default_text(self, text: str) -> None:
        return

    def set_append_text(self, text: str, prop: str | None) -> None:
        return

    def set_metadata(self, book: ExtendedEpubBook) -> None:
        return

    def check_validation(self):
        self.id_name = self.id_text.text()
        return

    def reset_metadata(self, book: ExtendedEpubBook) -> None:
        self.check_validation()
        return

class EpubFile(MetadataContents):
    win: QWidget
    epub_sel: Any = None

    def setup_ui(self, win: QWidget) -> None:
        super().setup_ui(win=win)
        self.main_box_layout.setTitle("Epub Files")

        epub_file_label = QLabel("Epubファイル")
        self.epub_path_text = QLineEdit()
        self.epub_path_text.setPlaceholderText("path/to/your/epub")
        self.button_pass = QPushButton("パスを選択...", win)
        self.button_pass.clicked.connect(lambda: self.ShowDialog())

        layout = QHBoxLayout()
        layout.addWidget(epub_file_label, 1)
        layout.addWidget(self.epub_path_text, 2)
        layout.addWidget(self.button_pass, 1)

        self.main_layout.addLayout(layout)

        v_main_layout = QVBoxLayout(win)
        v_main_layout.setContentsMargins(10, 10, 10, 0)
        v_main_layout.addWidget(self.main_box_layout)

    def set_epub_selected_method(self, m):
        self.epub_sel = m

    def ShowDialog(self):
        if self.epub_path_text.text() != "":
            text = self.epub_path_text.text()
        else:
            text = os.getcwd()
        fname = QFileDialog.getOpenFileName(
            self.win,
            "Open file",
            text,
            "epubファイル(*.epub)",
            options=QFileDialog.Option.DontUseNativeDialog,
        )
        self.epub_path_text.setText(fname[0])
        if fname[0] != "" and self.epub_sel:
            self.epub_sel(fname[0])

    def set_default_text(self, text: str) -> None:
        self.epub_path_text.setText(text)

class Title(MetadataContents):
    id_name: str = "title"
    title_name: str = ""
    title_yomi_name: str = ""

    def __init__(self, title_name: str = "", title_yomi_name: str = "") -> None:
        self.title_name = title_name
        self.title_yomi_name = title_yomi_name
        pass

    def setup_ui(self, win: QWidget) -> None:
        # layout = QFormLayout()
        # layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
        super().setup_ui(win=win)
        self.main_box_layout.setTitle("Title")
        title_label = QLabel("タイトル", None)
        title_yomi_label = QLabel("タイトル (カタカナ)", None)
        self.title_text = QLineEdit()
        self.title_text.setText(self.title_name)
        self.title_yomi_text = QLineEdit()
        self.title_yomi_text.setText(self.title_yomi_name)

        # layouts

        layout1 = QHBoxLayout()
        layout1.addWidget(title_label, 1)
        layout1.addWidget(self.title_text, 3)
        layout2 = QHBoxLayout()
        layout2.addWidget(title_yomi_label, 1)
        layout2.addWidget(self.title_yomi_text, 3)
        self.main_layout.addLayout(layout1)
        self.main_layout.addLayout(layout2)

        v_main_layout = QVBoxLayout(win)
        v_main_layout.setContentsMargins(10, 0, 10, 0)
        v_main_layout.addWidget(self.main_box_layout)
        self.add_footer()

    def set_default_text(self, text: str) -> None:
        self.title_name = text

    def set_append_text(self, text: str, prop: Optional[str]) -> None:
        if prop == "file-as":
            self.title_yomi_name = text

    def set_metadata(self, book: ExtendedEpubBook) -> None:
        book.add_metadata("DC", "title", self.title_text.text(), {"id": self.id_name})
        book.add_metadata(
            "OPF",
            None,
            self.title_yomi_text.text(),
            {"refines": "#" + self.id_name, "property": "file-as"},
        )

    def check_validation(self):
        super().check_validation()
        if self.title_text.text() == "":
            raise TextNotFoundException("タイトルを入れてください。")
        if self.title_yomi_text.text() != "" and self.id_name == "":
            raise TextNotFoundException("タイトルのIDを入力してください。")

    def reset_metadata(self, book: ExtendedEpubBook) -> None:
        super().reset_metadata(book=book)
        book.reset_metadata("DC", "title")

class Author(MetadataContents):
    id_name: str = "creator"
    num: int = 1
    creator_name: str = ""
    creator_yomi_name: str = ""

    def __init__(
        self, creator_name: str = "", creator_yomi_name: str = "", num: int = 1
    ) -> None:
        super().__init__()
        self.num = num
        self.creator_name = creator_name
        self.creator_yomi_name = creator_yomi_name

    def setup_ui(self, win: QWidget) -> None:
        super().setup_ui(win=win)
        self.main_box_layout.setTitle("Author")
        creator_label = QLabel("著者名" + str(self.num), None)
        creator_yomi_label = QLabel("著者名" + str(self.num) + "(カタカナ)", None)
        self.creator_text = QLineEdit()
        self.creator_text.setText(self.creator_name)
        self.creator_yomi_text = QLineEdit()
        self.creator_yomi_text.setText(self.creator_yomi_name)

        # layouts

        layout1 = QHBoxLayout()
        layout1.addWidget(creator_label, 1)
        layout1.addWidget(self.creator_text, 3)
        layout2 = QHBoxLayout()
        layout2.addWidget(creator_yomi_label, 1)
        layout2.addWidget(self.creator_yomi_text, 3)
        self.main_layout.addLayout(layout1)
        self.main_layout.addLayout(layout2)

        v_main_layout = QVBoxLayout(win)
        v_main_layout.setContentsMargins(10, 0, 10, 0)
        v_main_layout.addWidget(self.main_box_layout)
        self.add_footer()

    def set_default_text(self, text: str) -> None:
        self.creator_name = text

    def set_append_text(self, text: str, prop: str | None) -> None:
        if prop == "file-as":
            self.creator_yomi_name = text

    def set_metadata(self, book: ExtendedEpubBook) -> None:
        book.add_metadata(
            "DC", "creator", self.creator_text.text(), {"id": self.id_name}
        )
        book.add_metadata(
            "OPF",
            None,
            self.creator_yomi_text.text(),
            {"refines": "#" + self.id_name, "property": "file-as"},
        )
        book.add_metadata(
            "OPF",
            None,
            str(self.num),
            {"refines": "#" + self.id_name, "property": "display-seq"},
        )
        book.add_metadata(
            "OPF",
            None,
            "aut",
            {
                "refines": "#" + self.id_name,
                "property": "role",
                "scheme": "marc:relators",
            },
        )

    def check_validation(self):
        if self.creator_text.text() == "":
            raise TextNotFoundException("著者名を入れてください。")

    def reset_metadata(self, book: ExtendedEpubBook) -> None:
        super().reset_metadata(book=book)
        book.reset_metadata("DC", "creator")

class Publisher(MetadataContents):
    id_name: str = "publisher"
    publisher_name: str = ""
    publisher_yomi_name: str = ""

    def __init__(self, publisher_name: str = "", publisher_yomi_name: str = "") -> None:
        super().__init__()
        self.publisher_name = publisher_name
        self.publisher_yomi_name = publisher_yomi_name
        pass

    def setup_ui(self, win: QWidget) -> None:
        super().setup_ui(win=win)
        self.main_box_layout.setTitle("Publisher")
        publisher_label = QLabel("出版社", None)
        publisher_yomi_label = QLabel("出版社 (カタカナ)", None)
        self.publisher_text = QLineEdit()
        self.publisher_text.setText(self.publisher_name)
        self.publisher_yomi_text = QLineEdit()
        self.publisher_yomi_text.setText(self.publisher_yomi_name)

        # layouts

        layout1 = QHBoxLayout()
        layout1.addWidget(publisher_label, 1)
        layout1.addWidget(self.publisher_text, 3)
        layout2 = QHBoxLayout()
        layout2.addWidget(publisher_yomi_label, 1)
        layout2.addWidget(self.publisher_yomi_text, 3)
        self.main_layout.addLayout(layout1)
        self.main_layout.addLayout(layout2)

        v_main_layout = QVBoxLayout(win)
        v_main_layout.setContentsMargins(10, 0, 10, 0)
        v_main_layout.addWidget(self.main_box_layout)
        self.add_footer()

    def set_default_text(self, text: str) -> None:
        self.publisher_name = text

    def set_append_text(self, text: str, prop: str | None) -> None:
        if prop == "file-as":
            self.publisher_yomi_name = text

    def set_metadata(self, book: ExtendedEpubBook) -> None:
        book.add_metadata(
            "DC", "publisher", self.publisher_text.text(), {"id": self.id_name}
        )
        book.add_metadata(
            "OPF",
            None,
            self.publisher_yomi_text.text(),
            {"refines": "#" + self.id_name, "property": "file-as"},
        )

    def check_validation(self):
        if self.publisher_text.text() == "":
            raise TextNotFoundException("出版社を入れてください。")

    def reset_metadata(self, book: ExtendedEpubBook) -> None:
        super().reset_metadata(book=book)
        book.reset_metadata("DC", "publisher")

class Synopsis(MetadataContents):
    synopsis_name: str = ""

    def __init__(self, synopsis_name: str = "") -> None:
        super().__init__()
        self.synopsis_name = synopsis_name
        pass

    def setup_ui(self, win: QWidget) -> None:
        super().setup_ui(win=win)
        self.main_box_layout.setTitle("Synopsis")
        synopsis_label = QLabel("あらすじ", None)
        self.synopsis_text = QGrowingTextEdit()
        self.synopsis_text.textChanged.connect(lambda: self.synopsis_text.sizeChange())
        self.synopsis_text.setText(self.synopsis_name)

        # layouts
        layout = QHBoxLayout()
        layout.addWidget(synopsis_label, 1)
        layout.addWidget(self.synopsis_text, 3)
        self.main_layout.addLayout(layout)

        v_main_layout = QVBoxLayout(win)
        v_main_layout.setContentsMargins(10, 0, 10, 0)
        v_main_layout.addWidget(self.main_box_layout)
        self.add_footer()

    def set_metadata(self, book: ExtendedEpubBook) -> None:
        book.add_metadata("DC", "description", self.synopsis_text.toPlainText())

    def set_default_text(self, text: str) -> None:
        self.synopsis_name = text

    def reset_metadata(self, book: ExtendedEpubBook) -> None:
        book.reset_metadata("DC", "description")

class Identifier(MetadataContents):
    identifier_name: str

    def __init__(self, identifier_name: str = "") -> None:
        super().__init__()
        self.identifier_name = identifier_name
        pass

    def setup_ui(self, win: QWidget) -> None:
        super().setup_ui(win=win)
        self.main_box_layout.setTitle("Identifier")
        identifier_label = QLabel("Unique ID", None)
        self.identifier_text = QLineEdit()
        self.identifier_text.setText(self.identifier_name)
        self.identifier_text.setDisabled(True)
        self.generate_button = QPushButton("Generate")
        self.generate_button.clicked.connect(self.generate_uuid)

        # layouts
        layout = QHBoxLayout()
        layout.addWidget(identifier_label, 1)
        layout.addWidget(self.identifier_text, 3)
        self.main_layout.addLayout(layout)

        layout2 = QHBoxLayout()
        layout2.addStretch(3)
        layout2.addWidget(self.generate_button, 1)
        self.main_layout.addLayout(layout2)

        v_main_layout = QVBoxLayout(win)
        v_main_layout.setContentsMargins(10, 0, 10, 0)
        v_main_layout.addWidget(self.main_box_layout)

    def set_metadata(self, book: ExtendedEpubBook) -> None:
        book.add_metadata("DC", "identifier", self.identifier_text.text())

    def set_default_text(self, text: str) -> None:
        self.identifier_name = text

    def reset_metadata(self, book: ExtendedEpubBook) -> None:
        book.reset_metadata("DC", "identifier")

    def generate_uuid(self):
        import uuid

        self.identifier_text.setText("urn:uuid:" + str(uuid.uuid4()))

class Language(MetadataContents):
    language_name: str

    def __init__(self, language_name: str = "") -> None:
        super().__init__()
        self.language_name = language_name
        pass

    def setup_ui(self, win: QWidget) -> None:
        # layout = QFormLayout()
        # layout.setFieldGrowthPolicy(QFormLayout.FieldGrowthPolicy.AllNonFixedFieldsGrow)
        layout = QHBoxLayout()
        glayout = QGroupBox("Language")
        glayout.setLayout(layout)
        language_label = QLabel("language", None)
        self.language_text = QLineEdit()
        self.language_text.setText(self.language_name)

        # layouts

        layout.addWidget(language_label, 1)
        layout.addWidget(self.language_text, 3)

        v_main_layout = QVBoxLayout(win)
        v_main_layout.setContentsMargins(10, 0, 10, 0)
        v_main_layout.addWidget(glayout)

    def set_metadata(self, book: ExtendedEpubBook) -> None:
        book.add_metadata("DC", "language", self.language_text.text())

    def set_default_text(self, text: str) -> None:
        self.language_name = text

    def reset_metadata(self, book: ExtendedEpubBook) -> None:
        book.reset_metadata("DC", "language")

class Series(MetadataContents):
    series_name: str
    id_name: str = "Series"

    def __init__(self, series_name: str = "") -> None:
        super().__init__()
        self.series_name = series_name
        pass

    def setup_ui(self, win: QWidget) -> None:
        super().setup_ui(win=win)
        self.main_box_layout.setTitle("Series")
        series_label = QLabel("シリーズ", None)
        self.series_text = QLineEdit()
        self.series_text.setText(self.series_name)

        # layouts

        layout1 = QHBoxLayout()
        layout1.addWidget(series_label, 1)
        layout1.addWidget(self.series_text, 3)
        self.main_layout.addLayout(layout1)

        v_main_layout = QVBoxLayout(win)
        v_main_layout.setContentsMargins(10, 0, 10, 0)
        v_main_layout.addWidget(self.main_box_layout)
        self.add_footer()

    def set_metadata(self, book: ExtendedEpubBook) -> None:
        book.add_metadata(
            "OPF",
            None,
            self.series_text.text(),
            {"property": "belongs-to-collection", "id": self.id_name},
        )
        book.add_metadata(
            "OPF",
            None,
            "set",
            {"refines": "#" + self.id_name, "property": "collection-type"},
        )

    def set_default_text(self, text: str) -> None:
        self.series_name = text

    def reset_metadata(self, book: ExtendedEpubBook) -> None:
        return

def set_content(name: str) -> MetadataContents:
    content = MetadataContents()
    if name == "Title" or name == "title":
        content = Title()
    elif name == "Author" or name == "creator":
        content = Author()
    elif name == "Publisher" or name == "publisher":
        content = Publisher()
    elif name == "Synopsis" or name == "description":
        content = Synopsis()
    elif name == "Identifier" or name == "identifier":
        content = Identifier()
    elif name == "Language" or name == "language":
        content = Language()
    elif name == "Series" or name == "series":
        content = Series()
    else:
        # ERROR DEFAULT
        content = Title()

    return content

class metadata:
    author: str


import datetime
from typing import TYPE_CHECKING, Dict, Final, List, Optional, Sequence, Tuple

from telegram import constants
from telegram._chat import Chat
from telegram._messageentity import MessageEntity
from telegram._telegramobject import TelegramObject
from telegram._user import User
from telegram._utils import enum
from telegram._utils.argumentparsing import parse_sequence_arg
from telegram._utils.datetime import extract_tzinfo_from_defaults, from_timestamp
from telegram._utils.types import JSONDict

if TYPE_CHECKING:
    from telegram import Bot

class PollOption(TelegramObject):
    """
    This object contains information about one answer option in a poll.

    Objects of this class are comparable in terms of equality. Two objects of this class are
    considered equal, if their :attr:`text` and :attr:`voter_count` are equal.

    Args:
        text (:obj:`str`): Option text,
            :tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
            characters.
        voter_count (:obj:`int`): Number of users that voted for this option.

    Attributes:
        text (:obj:`str`): Option text,
            :tg-const:`telegram.PollOption.MIN_LENGTH`-:tg-const:`telegram.PollOption.MAX_LENGTH`
            characters.
        voter_count (:obj:`int`): Number of users that voted for this option.

    """

    __slots__ = ("voter_count", "text")

    def __init__(self, text: str, voter_count: int, *, api_kwargs: Optional[JSONDict] = None):
        super().__init__(api_kwargs=api_kwargs)
        self.text: str = text
        self.voter_count: int = voter_count

        self._id_attrs = (self.text, self.voter_count)

        self._freeze()

    MIN_LENGTH: Final[int] = constants.PollLimit.MIN_OPTION_LENGTH
    """:const:`telegram.constants.PollLimit.MIN_OPTION_LENGTH`

    .. versionadded:: 20.0
    """
    MAX_LENGTH: Final[int] = constants.PollLimit.MAX_OPTION_LENGTH
    """:const:`telegram.constants.PollLimit.MAX_OPTION_LENGTH`

    .. versionadded:: 20.0
    """

class PollAnswer(TelegramObject):
    """
    This object represents an answer of a user in a non-anonymous poll.

    Objects of this class are comparable in terms of equality. Two objects of this class are
    considered equal, if their :attr:`poll_id`, :attr:`user` and :attr:`option_ids` are equal.

    .. versionchanged:: 20.5
        The order of :paramref:`option_ids` and :paramref:`user` is changed in
        20.5 as the latter one became optional.

    .. versionchanged:: 20.6
       Backward compatiblity for changed order of :paramref:`option_ids` and :paramref:`user`
       was removed.

    Args:
        poll_id (:obj:`str`): Unique poll identifier.
        option_ids (Sequence[:obj:`int`]): Identifiers of answer options, chosen by the user. May
            be empty if the user retracted their vote.

            .. versionchanged:: 20.0
                |sequenceclassargs|
        user (:class:`telegram.User`, optional): The user that changed the answer to the poll,
            if the voter isn't anonymous. If the voter is anonymous, this field will contain the
            user :tg-const:`telegram.constants.ChatID.FAKE_CHANNEL` for backwards compatibility.

            .. versionchanged:: 20.5
                :paramref:`user` became optional.
        voter_chat (:class:`telegram.Chat`, optional): The chat that changed the answer to the
            poll, if the voter is anonymous.

            .. versionadded:: 20.5

    Attributes:
        poll_id (:obj:`str`): Unique poll identifier.
        option_ids (Tuple[:obj:`int`]): Identifiers of answer options, chosen by the user. May
            be empty if the user retracted their vote.

            .. versionchanged:: 20.0
                |tupleclassattrs|
        user (:class:`telegram.User`): Optional. The user, who changed the answer to the
            poll, if the voter isn't anonymous. If the voter is anonymous, this field will contain
            the user :tg-const:`telegram.constants.ChatID.FAKE_CHANNEL` for backwards compatibility

            .. versionchanged:: 20.5
                :paramref:`user` became optional.
        voter_chat (:class:`telegram.Chat`): Optional. The chat that changed the answer to the
            poll, if the voter is anonymous.

            .. versionadded:: 20.5

    """

    __slots__ = ("option_ids", "poll_id", "user", "voter_chat")

    def __init__(
        self,
        poll_id: str,
        option_ids: Sequence[int],
        user: Optional[User] = None,
        voter_chat: Optional[Chat] = None,
        *,
        api_kwargs: Optional[JSONDict] = None,
    ):
        super().__init__(api_kwargs=api_kwargs)
        self.poll_id: str = poll_id
        self.voter_chat: Optional[Chat] = voter_chat
        self.option_ids: Tuple[int, ...] = parse_sequence_arg(option_ids)
        self.user: Optional[User] = user

        self._id_attrs = (
            self.poll_id,
            self.option_ids,
            self.user,
            self.voter_chat,
        )

        self._freeze()

    @classmethod
    def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["PollAnswer"]:
        """See :meth:`telegram.TelegramObject.de_json`."""
        data = cls._parse_data(data)

        if not data:
            return None

        data["user"] = User.de_json(data.get("user"), bot)
        data["voter_chat"] = Chat.de_json(data.get("voter_chat"), bot)

        return super().de_json(data=data, bot=bot)

class Poll(TelegramObject):
    """
    This object contains information about a poll.

    Objects of this class are comparable in terms of equality. Two objects of this class are
    considered equal, if their :attr:`id` is equal.

    Examples:
        :any:`Poll Bot <examples.pollbot>`

    Args:
        id (:obj:`str`): Unique poll identifier.
        question (:obj:`str`): Poll question, :tg-const:`telegram.Poll.MIN_QUESTION_LENGTH`-
            :tg-const:`telegram.Poll.MAX_QUESTION_LENGTH` characters.
        options (Sequence[:class:`~telegram.PollOption`]): List of poll options.

            .. versionchanged:: 20.0
                |sequenceclassargs|
        is_closed (:obj:`bool`): :obj:`True`, if the poll is closed.
        is_anonymous (:obj:`bool`): :obj:`True`, if the poll is anonymous.
        type (:obj:`str`): Poll type, currently can be :attr:`REGULAR` or :attr:`QUIZ`.
        allows_multiple_answers (:obj:`bool`): :obj:`True`, if the poll allows multiple answers.
        correct_option_id (:obj:`int`, optional): A zero based identifier of the correct answer
            option. Available only for closed polls in the quiz mode, which were sent
            (not forwarded), by the bot or to a private chat with the bot.
        explanation (:obj:`str`, optional): Text that is shown when a user chooses an incorrect
            answer or taps on the lamp icon in a quiz-style poll,
            0-:tg-const:`telegram.Poll.MAX_EXPLANATION_LENGTH` characters.
        explanation_entities (Sequence[:class:`telegram.MessageEntity`], optional): Special
            entities like usernames, URLs, bot commands, etc. that appear in the
            :attr:`explanation`. This list is empty if the message does not contain explanation
            entities.

            .. versionchanged:: 20.0

               * This attribute is now always a (possibly empty) list and never :obj:`None`.
               * |sequenceclassargs|
        open_period (:obj:`int`, optional): Amount of time in seconds the poll will be active
            after creation.
        close_date (:obj:`datetime.datetime`, optional): Point in time (Unix timestamp) when the
            poll will be automatically closed. Converted to :obj:`datetime.datetime`.

            .. versionchanged:: 20.3
                |datetime_localization|

    Attributes:
        id (:obj:`str`): Unique poll identifier.
        question (:obj:`str`): Poll question, :tg-const:`telegram.Poll.MIN_QUESTION_LENGTH`-
            :tg-const:`telegram.Poll.MAX_QUESTION_LENGTH` characters.
        options (Tuple[:class:`~telegram.PollOption`]): List of poll options.

            .. versionchanged:: 20.0
                |tupleclassattrs|
        total_voter_count (:obj:`int`): Total number of users that voted in the poll.
        is_closed (:obj:`bool`): :obj:`True`, if the poll is closed.
        is_anonymous (:obj:`bool`): :obj:`True`, if the poll is anonymous.
        type (:obj:`str`): Poll type, currently can be :attr:`REGULAR` or :attr:`QUIZ`.
        allows_multiple_answers (:obj:`bool`): :obj:`True`, if the poll allows multiple answers.
        correct_option_id (:obj:`int`): Optional. A zero based identifier of the correct answer
            option. Available only for closed polls in the quiz mode, which were sent
            (not forwarded), by the bot or to a private chat with the bot.
        explanation (:obj:`str`): Optional. Text that is shown when a user chooses an incorrect
            answer or taps on the lamp icon in a quiz-style poll,
            0-:tg-const:`telegram.Poll.MAX_EXPLANATION_LENGTH` characters.
        explanation_entities (Tuple[:class:`telegram.MessageEntity`]): Special entities
            like usernames, URLs, bot commands, etc. that appear in the :attr:`explanation`.
            This list is empty if the message does not contain explanation entities.

            .. versionchanged:: 20.0
                |tupleclassattrs|

            .. versionchanged:: 20.0
               This attribute is now always a (possibly empty) list and never :obj:`None`.
        open_period (:obj:`int`): Optional. Amount of time in seconds the poll will be active
            after creation.
        close_date (:obj:`datetime.datetime`): Optional. Point in time when the poll will be
            automatically closed.

            .. versionchanged:: 20.3
                |datetime_localization|

    """

    __slots__ = (
        "total_voter_count",
        "allows_multiple_answers",
        "open_period",
        "options",
        "type",
        "explanation_entities",
        "is_anonymous",
        "close_date",
        "is_closed",
        "id",
        "explanation",
        "question",
        "correct_option_id",
    )

    def __init__(
        self,
        id: str,  # pylint: disable=redefined-builtin
        question: str,
        options: Sequence[PollOption],
        total_voter_count: int,
        is_closed: bool,
        is_anonymous: bool,
        type: str,  # pylint: disable=redefined-builtin
        allows_multiple_answers: bool,
        correct_option_id: Optional[int] = None,
        explanation: Optional[str] = None,
        explanation_entities: Optional[Sequence[MessageEntity]] = None,
        open_period: Optional[int] = None,
        close_date: Optional[datetime.datetime] = None,
        *,
        api_kwargs: Optional[JSONDict] = None,
    ):
        super().__init__(api_kwargs=api_kwargs)
        self.id: str = id
        self.question: str = question
        self.options: Tuple[PollOption, ...] = parse_sequence_arg(options)
        self.total_voter_count: int = total_voter_count
        self.is_closed: bool = is_closed
        self.is_anonymous: bool = is_anonymous
        self.type: str = enum.get_member(constants.PollType, type, type)
        self.allows_multiple_answers: bool = allows_multiple_answers
        self.correct_option_id: Optional[int] = correct_option_id
        self.explanation: Optional[str] = explanation
        self.explanation_entities: Tuple[MessageEntity, ...] = parse_sequence_arg(
            explanation_entities
        )
        self.open_period: Optional[int] = open_period
        self.close_date: Optional[datetime.datetime] = close_date

        self._id_attrs = (self.id,)

        self._freeze()

    @classmethod
    def de_json(cls, data: Optional[JSONDict], bot: "Bot") -> Optional["Poll"]:
        """See :meth:`telegram.TelegramObject.de_json`."""
        data = cls._parse_data(data)

        if not data:
            return None

        # Get the local timezone from the bot if it has defaults
        loc_tzinfo = extract_tzinfo_from_defaults(bot)

        data["options"] = [PollOption.de_json(option, bot) for option in data["options"]]
        data["explanation_entities"] = MessageEntity.de_list(data.get("explanation_entities"), bot)
        data["close_date"] = from_timestamp(data.get("close_date"), tzinfo=loc_tzinfo)

        return super().de_json(data=data, bot=bot)

    def parse_explanation_entity(self, entity: MessageEntity) -> str:
        """Returns the text from a given :class:`telegram.MessageEntity`.

        Note:
            This method is present because Telegram calculates the offset and length in
            UTF-16 codepoint pairs, which some versions of Python don't handle automatically.
            (That is, you can't just slice ``Message.text`` with the offset and length.)

        Args:
            entity (:class:`telegram.MessageEntity`): The entity to extract the text from. It must
                be an entity that belongs to this message.

        Returns:
            :obj:`str`: The text of the given entity.

        Raises:
            RuntimeError: If the poll has no explanation.

        """
        if not self.explanation:
            raise RuntimeError("This Poll has no 'explanation'.")

        entity_text = self.explanation.encode("utf-16-le")
        entity_text = entity_text[entity.offset * 2 : (entity.offset + entity.length) * 2]

        return entity_text.decode("utf-16-le")

    def parse_explanation_entities(
        self, types: Optional[List[str]] = None
    ) -> Dict[MessageEntity, str]:
        """
        Returns a :obj:`dict` that maps :class:`telegram.MessageEntity` to :obj:`str`.
        It contains entities from this polls explanation filtered by their ``type`` attribute as
        the key, and the text that each entity belongs to as the value of the :obj:`dict`.

        Note:
            This method should always be used instead of the :attr:`explanation_entities`
            attribute, since it calculates the correct substring from the message text based on
            UTF-16 codepoints. See :attr:`parse_explanation_entity` for more info.

        Args:
            types (List[:obj:`str`], optional): List of ``MessageEntity`` types as strings. If the
                    ``type`` attribute of an entity is contained in this list, it will be returned.
                    Defaults to :attr:`telegram.MessageEntity.ALL_TYPES`.

        Returns:
            Dict[:class:`telegram.MessageEntity`, :obj:`str`]: A dictionary of entities mapped to
            the text that belongs to them, calculated based on UTF-16 codepoints.

        """
        if types is None:
            types = MessageEntity.ALL_TYPES

        return {
            entity: self.parse_explanation_entity(entity)
            for entity in self.explanation_entities
            if entity.type in types
        }

    REGULAR: Final[str] = constants.PollType.REGULAR
    """:const:`telegram.constants.PollType.REGULAR`"""
    QUIZ: Final[str] = constants.PollType.QUIZ
    """:const:`telegram.constants.PollType.QUIZ`"""
    MAX_EXPLANATION_LENGTH: Final[int] = constants.PollLimit.MAX_EXPLANATION_LENGTH
    """:const:`telegram.constants.PollLimit.MAX_EXPLANATION_LENGTH`

    .. versionadded:: 20.0
    """
    MAX_EXPLANATION_LINE_FEEDS: Final[int] = constants.PollLimit.MAX_EXPLANATION_LINE_FEEDS
    """:const:`telegram.constants.PollLimit.MAX_EXPLANATION_LINE_FEEDS`

    .. versionadded:: 20.0
    """
    MIN_OPEN_PERIOD: Final[int] = constants.PollLimit.MIN_OPEN_PERIOD
    """:const:`telegram.constants.PollLimit.MIN_OPEN_PERIOD`

    .. versionadded:: 20.0
    """
    MAX_OPEN_PERIOD: Final[int] = constants.PollLimit.MAX_OPEN_PERIOD
    """:const:`telegram.constants.PollLimit.MAX_OPEN_PERIOD`

    .. versionadded:: 20.0
    """
    MIN_QUESTION_LENGTH: Final[int] = constants.PollLimit.MIN_QUESTION_LENGTH
    """:const:`telegram.constants.PollLimit.MIN_QUESTION_LENGTH`

    .. versionadded:: 20.0
    """
    MAX_QUESTION_LENGTH: Final[int] = constants.PollLimit.MAX_QUESTION_LENGTH
    """:const:`telegram.constants.PollLimit.MAX_QUESTION_LENGTH`

    .. versionadded:: 20.0
    """
    MIN_OPTION_LENGTH: Final[int] = constants.PollLimit.MIN_OPTION_LENGTH
    """:const:`telegram.constants.PollLimit.MIN_OPTION_LENGTH`

    .. versionadded:: 20.0
    """
    MAX_OPTION_LENGTH: Final[int] = constants.PollLimit.MAX_OPTION_LENGTH
    """:const:`telegram.constants.PollLimit.MAX_OPTION_LENGTH`

    .. versionadded:: 20.0
    """
    MIN_OPTION_NUMBER: Final[int] = constants.PollLimit.MIN_OPTION_NUMBER
    """:const:`telegram.constants.PollLimit.MIN_OPTION_NUMBER`

    .. versionadded:: 20.0
    """
    MAX_OPTION_NUMBER: Final[int] = constants.PollLimit.MAX_OPTION_NUMBER
    """:const:`telegram.constants.PollLimit.MAX_OPTION_NUMBER`

    .. versionadded:: 20.0
    """


import numpy as np
import cv2

cap = cv2.VideoCapture("C:\\Users\\kimin\\OneDrive\\바탕 화면\\sample.mp4")  # Open video file
print("(", cap.get(3), cap.get(4), ")")
bgSub = cv2.createBackgroundSubtractorKNN(dist2Threshold=400,detectShadows=False)
mid_x = int(cap.get(3)/2)
mid_y = int(cap.get(4)/2)
frame_sz = (500,300)
kernelOp = np.ones((3,3),np.uint8)
kernelCl = np.ones((11,11),np.uint8)
count = 0
cv2.namedWindow("Intermid_Demo",0)
while (cap.isOpened()):
    (ret, frame) = cap.read()  # read a frame
    try:
        frame = cv2.resize(frame, frame_sz)
        original = frame
        frame = cv2.cvtColor(frame,cv2.COLOR_RGB2GRAY)
        frame = cv2.GaussianBlur(frame,(3,3),0)
        frame = cv2.morphologyEx(frame, cv2.MORPH_OPEN, kernelOp)
        frame = cv2.morphologyEx(frame, cv2.MORPH_CLOSE, kernelCl)
        frame = bgSub.apply(frame)
        cv2.threshold(frame,200,255,cv2.THRESH_BINARY)
        frame = cv2.morphologyEx(frame,cv2.MORPH_OPEN,kernelOp)
        frame = cv2.morphologyEx(frame, cv2.MORPH_OPEN, kernelOp)
        frame = cv2.morphologyEx(frame, cv2.MORPH_CLOSE, kernelCl)
        frame = cv2.morphologyEx(frame, cv2.MORPH_CLOSE, kernelCl)
        _, contours, hierarchy = cv2.findContours(frame,cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_NONE)
        for cnt in contours:
            area = cv2.contourArea(cnt)
            if area>5000:
                M = cv2.moments(cnt)
                cx = int(M['m10'] / M['m00'])
                cy = int(M['m01'] / M['m00'])
                x, y, w, h = cv2.boundingRect(cnt)
                cv2.circle(original, (cx, cy), 3, (0, 0, 255), -1)
                cv2.rectangle(original, (x, y), (x + w, y + h),(0,255,2),2)
        original = cv2.line(original,(400,0),(400,300),(255,0,0),2)
        original = cv2.putText(original,"IN",(350,250),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),2)
        original = cv2.putText(original, "OUT", (420, 250), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
        cv2.imshow("Intermid_Demo", original)
    except:
        print("EOF")
        break
    k = cv2.waitKey(30)
    if k > 0:
        a = int(input("1누르면 재진행"))
        continue
cap.release()  # release video file
cv2.destroyAllWindows()


from django.db.models import Sum
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django_filters.rest_framework import DjangoFilterBackend
from djoser.views import UserViewSet
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.filters import SearchFilter
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.viewsets import ModelViewSet

from recipes.models import (ShoppingCart, Favorite, Ingredient,
                            IngredientsInRecipe,
                            Recipe, Tag)
from users.models import Follow, User
from .filters import FilterSearchForName, FilterRecipes

from .pagination import LimitUserPagination
from .permission import AuthorOrReadOnly
from .serializers import (ShoppingCartSerializer, CustomUserSerializer,
                          FavoriteSerializer, IngredientSerializer,
                          RecipesSerializerPost, FollowSerializerPost,
                          RecipeSerializer, SubscribeUserSerializer,
                          TagSerializer)

class CustomUserViewSet(UserViewSet):
    """Вью юсеров."""

    queryset = User.objects.all()
    serializer_class = CustomUserSerializer
    pagination_class = LimitUserPagination
    permission_classes = (AuthorOrReadOnly,)

    def get_permissions(self):
        """Выбор пермишена."""
        if self.action == 'create':
            return [AllowAny(), ]
        if self.action == 'me':
            return [IsAuthenticated(), ]
        return [AuthorOrReadOnly(), ]

    @action(methods=['post', 'delete'],
            permission_classes=(IsAuthenticated,),
            detail=True)
    def subscribe(self, request, id):
        """Подписка."""
        get_object_or_404(User, id=id)
        if request.method == 'POST':
            user = request.user
            data = {'user': user.id, 'author': id}
            serializer = FollowSerializerPost(data=data,
                                              context={'request': request})
            serializer.is_valid(raise_exception=True)
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)

        data = Follow.objects.filter(user_id=request.user.id,
                                     author_id=id)
        if request.method == 'DELETE':
            if data.exists():
                data.delete()
                return Response({'Отлично': 'Вы отписаны от автора'},
                                status=status.HTTP_204_NO_CONTENT)
            return Response({'Ошибка': 'Неправильные данные'},
                            status=status.HTTP_400_BAD_REQUEST)

    @action(methods=['get'],
            permission_classes=(IsAuthenticated,),
            detail=False, )
    def subscriptions(self, request):
        """Все подписки пользователя."""
        page = self.paginate_queryset(User.objects.filter(
            following__user=request.user))
        serializer = SubscribeUserSerializer(page, many=True,
                                             context={'request': request})
        return self.get_paginated_response(serializer.data)

class TagViewSet(viewsets.ReadOnlyModelViewSet):
    """Вью тегов."""

    queryset = Tag.objects.all()
    serializer_class = TagSerializer
    pagination_class = None

class IngredientsViewsSet(viewsets.ReadOnlyModelViewSet):
    """Вью ингредиентов."""

    queryset = Ingredient.objects.all()
    serializer_class = IngredientSerializer
    pagination_class = None
    filter_backends = (DjangoFilterBackend, SearchFilter)
    search_fields = ('^name',)
    filterset_class = FilterSearchForName

class RecipeViewsSet(ModelViewSet):
    """Вью рецептов."""

    queryset = Recipe.objects.all()
    permission_classes = (AuthorOrReadOnly,)
    filter_backends = (DjangoFilterBackend,)
    filterset_class = FilterRecipes
    pagination_class = LimitUserPagination

    def get_serializer_class(self):
        """Выбор серилизатора."""
        if self.request.method == 'PATCH' or self.request.method == 'POST':
            return RecipesSerializerPost
        return RecipeSerializer

    def perform_create(self, serializer):
        """Создание рецепта."""
        serializer.save(author=self.request.user)

    def perform_update(self, serializer):
        """Изменение созданного рецепта."""
        serializer.save(author=self.request.user)

    @staticmethod
    def favorite_shoppingcart_funk(serializer, request, pk):
        """Дублирующийся код в избранном и корзине."""
        data = {'user': request.user.id, 'recipe': pk}
        context = {'request': request}
        serializer = serializer(data=data, context=context)
        serializer.is_valid(raise_exception=True)
        serializer.save()
        return Response(serializer.data, status=status.HTTP_201_CREATED)

    @action(methods=['post', 'delete'],
            permission_classes=[IsAuthenticated],
            detail=True)
    def favorite(self, request, pk):
        """Добавление/удаление избранных рецептов."""
        if request.method == 'POST':
            return self.favorite_shoppingcart_funk(
                FavoriteSerializer, request, pk)
        if request.method == "DELETE":
            data = Favorite.objects.filter(user=request.user, recipe__id=pk)
            if data.exists():
                data.delete()
                return Response(status=status.HTTP_204_NO_CONTENT)
        get_object_or_404(Recipe, id=pk)
        return Response({'ошибка': 'Нет такого рецепта'},
                        status=status.HTTP_400_BAD_REQUEST)

    @action(methods=['post', 'delete'],
            permission_classes=[IsAuthenticated],
            detail=True,
            )
    def shopping_cart(self, request, pk):
        """Добавление/удаление рецепта в корзину."""
        if request.method == 'POST':
            return self.favorite_shoppingcart_funk(
                ShoppingCartSerializer, request, pk)
        if request.method == 'DELETE':
            data = ShoppingCart.objects.filter(
                user_id=request.user.id, recipe_id=pk)
            if data.exists():
                data.delete()
                return Response(status=status.HTTP_204_NO_CONTENT)
        get_object_or_404(Recipe, id=pk)
        return Response(status=status.HTTP_400_BAD_REQUEST)

    @action(methods=['get'],
            permission_classes=[IsAuthenticated],
            detail=False)
    def download_shopping_cart(self, request):
        """Скачивание рецепта."""
        if request.user.shopping_cart.exists():
            ingredients_recipe = IngredientsInRecipe.objects.filter(
                recipe__shopping_cart__user=request.user)
            values = ingredients_recipe.values('ingredient__name',
                                               'ingredient__measurement_unit')
            ingredients = values.annotate(amount=Sum('amount'))

            shop_cart = 'Список покупок.\n'
            for ingredient in ingredients:
                shop_cart += (f'{ingredient["ingredient__name"]} - '
                              f'{ingredient["ingredient__measurement_unit"]} '
                              f'({ingredient["amount"]})\n')

            filename = 'Покупоки.txt'
            response = HttpResponse(shop_cart,
                                    content_type='text/plain')
            response['Content-Disposition'] = (f'attachment; '
                                               f'filename={filename}')
            return response
        return Response(status=status.HTTP_400_BAD_REQUEST)


from tkinter import *

win = Tk()
win.title('Ung dung post on FB')
# win.configure(width=500, height =600)
# win.geometry('500x600+600+100')
# win.resizable(width=False, height=True)
scrW = win.winfo_screenwidth()
scrH = win.winfo_screenheight()

win.geometry('500x600+%d+%d' %(scrW/2-250,scrH/2-300))

win.mainloop()


#!/usr/bin/env python
# encoding: utf-8
import cv2
import numpy as np

"""motion_attention_point_detector.
This is ease motion attention point calculation algorithm.

Example:
        $ python motion_attention_point.py

Todo:
    * cca function

"""
__author__ = "Kevin Lee"
__affiliate__ = "lidengju.com"
__copyright__ = "Copyright 2018, Kevin"
__license__ = "MIT"
__version__ = "0.1"
__maintainer__ = "Kevin Lee"
__email__ = "kevinleeex@foxmail.com"
__status__ = "Development"

class MotionAttention:
    def onChanged(self, val):
        self.diff_thresh = val

    def __init__(self, diff_thresh=25, move_sense=0.05, show_window=True):
        self.contour_area = []
        self.writer = None
        self.font = None
        self.diff_thresh = diff_thresh
        self.move_sense = move_sense
        self.init_flag = True
        self.show_window = show_window
        self.frame = None
        self.frame_w = None  # frame width
        self.frame_h = None  # frame height
        self.frame_area = None  # frame area
        self.gray_frame = None
        self.fusion_frame = None
        self.absdiff_frame = None
        self.previous_frame = None
        self.attention_point = None
        self.cur_contour = None
        self.cur_area = 0

        # show window with track bar.
        if show_window:
            self.window_name = "Motion Detection Result"
            cv2.namedWindow(self.window_name)
            cv2.createTrackbar("Difference Threshold", self.window_name, self.diff_thresh, 100, self.onChanged)

    def setFrame(self):
        """
        Init the frame setting.
        :return:
        """
        self.frame_w = self.frame.shape[1]  # frame width
        self.frame_h = self.frame.shape[0]  # frame height
        self.frame_area = self.frame_w * self.frame_h  # frame area
        self.gray_frame = cv2.cvtColor(self.frame, cv2.COLOR_BGR2GRAY)
        self.fusion_frame = np.float32(self.frame)

    def run(self, frame):
        """
        one run
        :param frame: current frame
        :return: current frame with contours, gray frame with motion variation, attention point
        """
        if self.init_flag:
            self.frame = frame
            self.setFrame()
        cur_frame = frame
        diff_frame = cv2.cvtColor(self.gray_frame, cv2.COLOR_GRAY2BGR)
        self.processFrame(cur_frame)

        if self.hasMoved():
            print("Object moved")
            self.attention_point = self.calAttentionPoint()
            print(self.attention_point)
            # draw attention point
            cv2.circle(cur_frame, (self.attention_point[0], self.attention_point[1]), 5, (0, 255, 0), -1)
            cv2.circle(diff_frame, (self.attention_point[0], self.attention_point[1]), 5, (0, 255, 0), -1)

        if self.show_window:
            cv2.drawContours(cur_frame, self.cur_contour, -1, (0, 0, 255), 2)
            cv2.imshow(self.window_name, cur_frame)
            cv2.imshow("Diff", diff_frame)

        return cur_frame, self.gray_frame, self.attention_point

    def processFrame(self, cur_frame):
        """
        Image processing, blur--> compute diff--> gray_frame--> dilation--> eroding
        :param cur_frame: current frame
        :return: result
        """
        cv2.blur(cur_frame, (3, 3), cur_frame)
        # first time to compute
        if self.init_flag:
            self.init_flag = False
            self.absdiff_frame = cur_frame.copy()
            self.previous_frame = cur_frame.copy()
        else:
            cv2.accumulateWeighted(cur_frame, self.fusion_frame, 0.05)  # compute average
        self.previous_frame = cv2.convertScaleAbs(self.fusion_frame)
        self.absdiff_frame = cv2.absdiff(cur_frame, self.previous_frame)  # abs(average - cur_frame)

        self.gray_frame = cv2.cvtColor(self.absdiff_frame, cv2.COLOR_BGR2GRAY)
        cv2.threshold(self.gray_frame, self.diff_thresh, 255, cv2.THRESH_BINARY, self.gray_frame)

        # define kernels
        dilate_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (15, 15))
        erode_kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (10, 10))
        cv2.dilate(self.gray_frame, dilate_kernel, self.gray_frame)  # dilation
        result = cv2.erode(self.gray_frame, erode_kernel, self.gray_frame)  # eroding
        return result

    def hasMoved(self):
        """
        Judge the object is moving or not.
        :return: is_moved(boolean)
        """
        out, contours, hierarchy = cv2.findContours(self.gray_frame, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
        self.cur_contour = contours
        max_contour = []
        contour_areas = []  # area of contours
        self.cur_area = 0
        for i in range(len(contours)):
            c = cv2.contourArea(contours[i])
            if c < 0.005 * self.frame_area:
                # fill small white blob in black
                cv2.drawContours(self.gray_frame, contours, i, (0, 0, 0), -1)
            else:
                contour_areas.append(c)
                max_contour.append(contours[i])
                self.cur_area += c
        self.cur_contour = max_contour
        self.contour_area = contour_areas
        val_area = self.cur_area / self.frame_area  # calculate the ratio of cur_area/frame_area

        if val_area > self.move_sense:
            return True
        else:
            return False

    def calAttentionPoint(self):
        """
        Calculate the weighted mean attention point.
        :return: attention point
        """
        # get center coord of contours
        centroids = []
        area_ratios = []
        for ix, contour in enumerate(self.cur_contour):
            mm = cv2.moments(contour)
            cx = int(mm['m10'] / mm['m00'])
            cy = int(mm['m01'] / mm['m00'])
            center = [cx, cy]
            a_ratio = 0
            if self.cur_area != 0:
                # print(self.cur_area)
                a_ratio = self.contour_area[ix] / self.cur_area
            centroids.append(center)
            area_ratios.append(a_ratio)

        attention_point = np.dot(np.array(area_ratios).transpose(), np.array(centroids)).squeeze()
        return np.int32(attention_point)

    def cca(self, src_img):
        """
        connected component analysis
        :param src_img: source image to analysis
        :return:
        """
        contours = self.cur_contour
        for i in range(len(contours)):
            pass

if __name__ == '__main__':
    detect = MotionAttention(diff_thresh=40, move_sense=0.01)
    capture = cv2.VideoCapture(0)
    while True:
        frame = capture.read()[1]
        detect.run(frame)
        c = cv2.waitKey(1) & 0x100
        if c == 27:
            break


#!/usr/bin/new python
import pika

connection = pika.BlockingConnection(
    pika.ConnectionParameters(host='localhost')
)

chane = connection.channel()

chane.queue_declare(queue = 'test')

chane.basic_publish(exchange='', routing_key='test', body='This is a test message')

chane.close()


import sys
from collections import deque

# sys.stdin = open("input.txt", "r")
input = sys.stdin.readline

adjacent_list = [[] for _ in range(1000 + 1)]
is_visited = [False for _ in range(1000 + 1)]

def read_input():
    global N, M, src
    N, M, src = map(int, input().rstrip().split())
    for _ in range(M):
        u, v = map(int, input().rstrip().split())
        adjacent_list[u].append(v)
        adjacent_list[v].append(u)

def init_is_visisted():
    for i in range(1000 + 1):
        is_visited[i] = False

def DFS():
    adjacent_list_for_DFS = [sorted(l, reverse=True) for l in adjacent_list]
    stack = []
    stack.append(src)

    while stack:
        cur = stack.pop()
        if is_visited[cur]:
            continue

        for nv in adjacent_list_for_DFS[cur]:
            stack.append(nv)
        is_visited[cur] = True
        print(cur, end=" ")

def BFS():
    adjacent_list_for_BFS = [sorted(l) for l in adjacent_list]
    queue = deque()
    queue.append(src)

    while queue:
        cur = queue.popleft()
        if is_visited[cur]:
            continue

        for nv in adjacent_list_for_BFS[cur]:
            queue.append(nv)

        is_visited[cur] = True
        print(cur, end=" ")

def solve():
    DFS()
    print()
    init_is_visisted()
    BFS()

if __name__ == "__main__":
    read_input()
    solve()


import numpy as np
import pandas as pd
import math

def solveSimplex(model,logger):
    #load problem set
    C= np.array([model['objCoef']])
    A= np.array(model['constCoef'])
    b= np.array([model['constRHS']])
    varId= np.array(model['realVar'])

    solTracker=[]
    sol = [0]*C.size

     # sets the basic variables up
    B_index=basicVarStart(C,A,varId)
    if B_index==[]:
        return {'problemSolved':False,'iteratedSol': None}
    else:
        logger.info(B_index)
        B=np.array([list(A[:,[i]].T[0]) for i in B_index]).T

    Z=0
    check=True
    count=1

    # solution iteration
    while(check):
        B_inv_A =np.dot(np.linalg.inv(B),A)
        B_inv_b_T =np.dot(np.linalg.inv(B),b.T)
        C_B=np.array([[C[0,i]for i in B_index]])

        sol=[B_inv_b_T[list(B_index).index(i),0] if(i in B_index) else 0 for i in range(len(sol))]
        solTracker.append(sol)

        new_C=np.subtract(np.dot(C_B,B_inv_A),C)
        Z=np.dot(C_B,B_inv_b_T)

        check=np.any(new_C<0)

        if(check):
            new_index_C=np.argmin(new_C)
            extracted_col=B_inv_A[:,[new_index_C]]
            for i in range(len(extracted_col)):
                if(extracted_col[i,0]<0):
                    extracted_col[i,0]=0
            B_index_leave= np.argmin(np.divide(B_inv_b_T,extracted_col))
            B=newB(B,B_index_leave,A,B_index,new_index_C)

        if(count>100):
            return {'problemSolved':False,'iteratedSol': solTracker}
        count+=1

    return {'problemSolved':True,'iteratedSol': solTracker}

# basic variable set for the start
def basicVarStart(C,A,varId):
    isSlackArt=(varId==0)
    temp=[]
    count=0
    numConst=A.shape[0]

    isBV=[list(A[:,[i]]==1).index(True) if((np.sum(A[:,[i]]==0)==(numConst-1)) & (np.sum(A[:,[i]]==1)==1) &(isSlackArt[i])) else -1 for i in range(A.shape[1])]
    #print(isBV)

    if (np.sum(np.array(isBV)>=0)==numConst):
        newBV=sorted([[isBV[i],i] for i in range(len(isBV))], key=lambda x: x[0])
        for x in newBV:
            if(x[0]>=0):
                temp.append(x[1])

    return temp

# select new basic variables
def newB(B,B_index_leave,A,B_index,new_index_C):
    temp=B[:,[]]
    for i in range(A.shape[0]):
        if(B_index_leave==i):
            temp=np.append(temp,A[:,[new_index_C]],axis=1)
            B_index[i]=new_index_C
        else:
            temp=np.append(temp,B[:,[i]],axis=1)
    return temp

def sampleProbSol():
    M=9999
    C=np.array([[5,4,0,0,0,-M]])
    A=np.array([[2,1,1,0,0,0],
                [1,1,0,1,0,0],
                [1,2,0,0,-1,1]])
    b=np.array([[20,18,12]])

    #var and index
    varlist = ["X1","X2","S1","S2","S3","A3"]
    sol = [0] * C.size

    # sets the basic variables up
    B_index=basicVarStart(C,A)
    if B_index==[]:
        print(warning)
    else:
        print(B_index)
        B=np.array([list(A[:,[i]].T[0]) for i in B_index]).T

    Z=0
    check=True
    count=0
    print("Beginning basic variable columns",B,sep="\n")
    while(check):
        B_inv_A =np.dot(np.linalg.inv(B),A)
        B_inv_b_T =np.dot(np.linalg.inv(B),b.T)
        C_B=np.array([[C[0,i]for i in B_index]])

        sol=[B_inv_b_T[list(B_index).index(i),0] if(i in B_index) else 0 for i in range(len(sol))]

        new_C=np.subtract(np.dot(C_B,B_inv_A),C)
        Z=np.dot(C_B,B_inv_b_T)

        print("inverse matrix B times matrix A",B_inv_A, sep="\n")
        print("\ninverse matrix B times inverted b",B_inv_b_T,sep="\n")
        print("\ncoefficients of Basic variables",C_B,sep="\n")
        print("\nnew coefficients",new_C,sep="\n")
        print("\ntemp sol",Z)
        check=np.any(new_C<0)

        if(check):
            new_index_C=np.argmin(new_C)
            print("\nnew variable coming in : ",varlist[new_index_C])
            extracted_col=B_inv_A[:,[new_index_C]]
            print("\ncolumn from constraints : ",extracted_col,sep="\n")
            for i in range(len(extracted_col)):
                if(extracted_col[i,0]<0):
                    extracted_col[i,0]=0
            B_index_leave= np.argmin(np.divide(B_inv_b_T,extracted_col))
            B=newB(B,B_index_leave,A,B_index,new_index_C)
            print("\nnew basic variable columns",B,sep="\n")

        count+=1
        print("\nend of iteration #",count)
        print("*******************************************\n\n")
        if(count>5): break
    return 0


# **************************************************************************
# *
# * Authors:     J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
# *
# * Unidad de  Bioinformatica of Centro Nacional de Biotecnologia , CSIC
# *
# * This program is free software; you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation; either version 2 of the License, or
# * (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program; if not, write to the Free Software
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
# * 02111-1307  USA
# *
# *  All comments concerning this program package may be sent to the
# *  e-mail address 'scipion@cnb.csic.es'
# *
# **************************************************************************
"""
Several Image tools using Matplotlib.
"""

import Tkinter as tk
import matplotlib
import numpy as np

try:
    matplotlib.use('TkAgg')
    from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
    import matplotlib.pyplot as plt
except:
    #matplotlib.use('Agg')
    plt = None

from matplotlib.figure import Figure
import matplotlib.cm as cm
from matplotlib.patches import Wedge

import xmippLib
import pyworkflow.gui as pwgui

class FigureFrame(tk.Frame):
    """ Create a Tk Frame that will contains a
    Matplotlib Figure.
    **kwargs arguments will be passed to Figure constructor.
    Valid options are:
        figsize = (xdim, ydim)
        dpi = value
        frameon = (True|False)
    """
    def __init__(self, parent, **kwargs):
        tk.Frame.__init__(self, parent)
        self.figure = Figure(**kwargs)
        self.canvas = FigureCanvasTkAgg(self.figure, master=self)
        self.canvas.get_tk_widget().grid(column=0, row=0)

    def getFigure(self):
        return self.figure

    def getCanvas(self):
        return self.canvas

class Preview(tk.Frame):
    #def __init__(self, parent, dim, dpi=36, label=None):
    def __init__(self, parent, dim, dpi=36, label=None, col=0, row=0):
        tk.Frame.__init__(self, parent)
        self.dim = dim
        self.bg = np.zeros((int(dim), int(dim)), float)
        ddim = dim/dpi
        self.figure = Figure(figsize=(ddim, ddim), dpi=dpi, frameon=False)
        self.canvas = FigureCanvasTkAgg(self.figure, master=self)
        self.canvas.get_tk_widget().grid(column=0, row=0)#, sticky=(N, W, E, S))
        if label:
            tk.Label(self, text=label).grid(column=0, row=1)
        self._createAxes()

    def setWindowTitle(self,title):
        """ Set window title"""
        self.canvas.set_window_title(title)

    def _createAxes(self):
        """ Should be implemented in subclasses. """
        pass

    def _update(self):
        """ Should be implemented in subclasses. """
        pass

    def clear(self):
        self._update(self.bg)

    def updateData(self, Z):
        self.clear()
        self._update(Z)

class ImagePreview(Preview):
    def __init__(self, parent, dim, dpi=36, label=None, col=0):
        Preview.__init__(self, parent, dim, dpi, label, col)

    def _createAxes(self):
        ax = self.figure.add_axes([0,0,1,1], frameon=False)
        self.figureimg = ax.imshow(self.bg, cmap=cm.gray)#, extent=[-h, h, -h, h])
        ax.set_axis_off()
        self.ax = ax

    def _update(self, Z, *args):
        self.figureimg.set_data(Z)
        self.figureimg.autoscale()
        self.figureimg.set(extent=[0, Z.shape[1], 0, Z.shape[0]])
        self.canvas.draw()

class PsdPreview(Preview):
    def __init__(self, master, dim, lf, hf, dpi=72, label="PSD", Z=None):
        Preview.__init__(self, master, dim, dpi, label)
        self.lf = lf
        self.hf = hf
        if self.ring:
            self.createRing()
        else:
            self.canvas.draw()

    def _createAxes(self):
        #axdef = SubplotZero(self.figure, 111)
        #ax = self.figure.add_subplot(axdef)
        ax = self.figure.add_axes([0.1,0.1,0.8,0.8], frameon=False)
        #ax.xaxis.set_offset_position(0.5)
        #ax.set_position([0, 0, 1, 1])
        h = 0.5
        ax.set_xlim(-h, h)
        ax.set_ylim(-h, h)
        ax.grid(True)
        self.ring = None
        self.img = ax.imshow(self.bg, cmap=cm.gray, extent=[-h, h, -h, h])
        self.ax = ax

    def createRing(self):
        radius = float(self.hf)
        width = radius - float(self.lf)
        self.ring = Wedge((0,0), radius, 0, 360, width=width, alpha=0.15) # Full ring
        self.ax.add_patch(self.ring)
        self.canvas.draw()

    def updateFreq(self, lf, hf):
        self.lf = lf
        self.hf = hf
        if self.ring:
            self.ring.remove()
            self.ring = None
        if self.hf:
            self.createRing()

    def _update(self, Z):
        if self.ring:
            self.ring.remove()
            self.ring = None
        if self.hf:
            self.createRing()
        self.img.set_data(Z)
        self.img.autoscale()
        self.canvas.draw()

class MaskPreview(ImagePreview):
    def __init__(self, parent, dim, dpi=36, label=None, col=0, outerRadius=None, innerRadius=0):
        ImagePreview.__init__(self, parent, dim, dpi, label, col)
        if outerRadius is None:
            outerRadius = dim / 2
        self.ring = None
        self.updateMask(outerRadius, innerRadius)

    def updateMask(self, outerRadius, innerRadius=0):
        if self.ring is not None:
            self.ring.remove()
        center = self.dim / 2
        width = outerRadius - innerRadius
        self.ring = Wedge((center, center), outerRadius, 0, 360, width=width, alpha=0.15) # Full ring
        self.ax.add_patch(self.ring)
        self.canvas.draw()

class ImageWindow(pwgui.Window):
    #TODO: change filename (now with xmipp format) by location (index, fn as in scipion)
    # this will require to put a wrapper to readPreview in ImageHandler that accepts location
    def __init__(self, filename=None, dim=256, dpi=96, image=None, label=None):
        pwgui.Window.__init__(self, minsize=None)
        if image is None:
            image = xmippLib.Image()
            if dim is None:
                image.read(filename)
                dim, _, _, _ = image.getDimensions()
            else:
                image.readPreview(filename, dim)

        dpi = min(dpi, dim)

        self.imagePreview = ImagePreview(self.root, dim, dpi, label, 0)

        if image is None and filename is None:
            raise Exception("ImageWindow: image or filename should be provided.")

        if filename is None:
            filename = "No filename"

        self.updateImage(image)

        self.imagePreview.grid(row=0, column=0)

    def updateData(self, Z):
        self.imagePreview.updateData(Z)

    def updateImage(self, image):
        self.updateData(image.getData())

def getPngData(filename):
    import matplotlib.image as mpimg
    return mpimg.imread(filename)

def createBgImage(dim):
    return np.ones((dim, dim, 3))


cities = {
    'Shanghai': {
        'country': 'China',
        'population': '2000w',
        'fact': 'economic center in China',
    },

    'Beijing': {
        'country': 'China',
        'population': '3000w',
        'fact': 'capital in China',
    },

    'Shenzhen': {
        'country': 'China',
        'population': '1500w',
        'fact': 'city of 996',
    },
}

for name, detail in cities.items():
    print(f"This city is {name.title()}")
    country = detail['country']
    population = detail['population']
    fact = detail['fact']

    print(f"\tIt belong to {country}")
    print(f"\tIt has {population} people live here")
    print(f"\tWhat interesting most is, it is the {fact}")

    print()


from typing import Optional, List
from datetime import datetime
from pydantic import BaseModel

class ChatModel(BaseModel):
	id: int
	socket: str
	user: str
	active: bool
	recent: Optional[bool] = None
	language: str

class MessageModel(BaseModel):
	id: int
	socket: str
	content: str
	read: bool
	operator: bool
	file: bool
	stream: bool
	href: Optional[bool] = None
	date: datetime
	chat_id: Optional[int] = None

class TemplateModel(BaseModel):
	id: int
	message: str
	language: str

	class Config:
		from_attributes = True

class OperatorModel(BaseModel):
	id: int
	name: str
	username: str
	templates: Optional[List[TemplateModel]] = None

	class Config:
		from_attributes = True


class Customer:
    name = ""
    lastName = ""
    age = 0

    def addCart(self):
        print("Added to " , self.name , " " , self.lastName  , "'s cart")

customer1 = Customer()
customer1.name = "Bob"
customer1.lastName = "Ba"
customer1.age = 8

customer1.addCart()

customer2 = Customer()
customer2.name = "Boy"
customer2.lastName = "Ba"
customer2.age = 8

customer2.addCart()

customer3 = Customer()
customer3.name = "Boyo"
customer3.lastName = "Ba"
customer3.age = 8

customer3.addCart()

customer4 = Customer()
customer4.name = "Bopo"
customer4.lastName = "Ba"
customer4.age = 8

customer4.addCart()


from tkinter import *
from tkinter import colorchooser
import tkinter.filedialog
import PIL
from PIL import Image, ImageDraw
from tkinter.ttk import *
import time
import backend as bd

root = Tk()
root.title('Loading')
root.geometry('300x100')
root.iconbitmap("./assets/logo.ico")

label = Label(root, text = "Copyright © YTP Studio")
label.pack()

pb1 = Progressbar(root, orient=HORIZONTAL, length=100, mode='indeterminate')
pb1.pack(expand=True)

def progress():
    for i in range(5):
        root.update_idletasks()
        pb1['value'] += 20
        time.sleep(0.2)
    root.destroy()
    main()

def main():

    bd.Paint()

Button(root, text='Start Painting', command=progress).pack()

root.mainloop()


import cv2
import numpy as np

img = cv2.imread('chessboard18.JPG')
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# prepare object points
obj_grid = np.zeros((6 * 9, 3), np.float32)
obj_grid[:, :2] = np.mgrid[0:9, 0:6].T.reshape(-1, 2)

# Arrays to store object  and image points
objpoints = []
imgpoints = []

# find chessboard corners
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)  # termination criteria
retval, corners = cv2.findChessboardCorners(gray, (9, 6), None)
if retval:
    objpoints.append(obj_grid)
    corners2 = cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
    imgpoints.append(corners2)
    # plot corners
    img = cv2.drawChessboardCorners(img, (7, 6), corners2, retval)
    cv2.imshow('img', img)
    cv2.waitKey(2000)

# calibrate camera
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(objpoints, imgpoints, gray.shape[::-1], None, None)

def undistort(img, mtx, dist):
    h, w = img.shape[:2]
    newcameramtx, roi = cv2.getOptimalNewCameraMatrix(mtx, dist, (w, h), 1, (w, h))
    dst = cv2.undistort(img, mtx, dist, None, mtx)
    x, y, w, h = roi
    # dst = dst[y:y+h, x:x+w]
    # cv2.imshow('img', img)
    # cv2.imshow('Result.png', dst)
    # cv2.waitKey(5000)

    # plt.figure(figsize =(10, 5))
    # plt.subplot(121)
    # plt.imshow(img)
    # plt.title('Original')
    # plt.subplot(122)
    # plt.imshow(dst)
    # plt.title('Corrected')
    # plt.show()

# print("Camera matrix: \n", mtx)
# Calculate Focal Length in mm F = (fx in pixels/img width in pixels) * Sensor Width (22.3)
focal_length = int(mtx[0, 0] / img.shape[1] * 22.3)
print('\nFocal Length:', focal_length, "mm")


import glob
from glob import glob
import PIL

dir_new = '/Users/rsimons/Desktop/clear/figures/linepng'
dir_old = '/Users/rsimons/Desktop/linepng'

out_dir = '/Users/rsimons/Desktop/clear/figures/linepng/out'
#imgs_comb = np.vstack((np.asarray( i.resize(min_shape) ) for i in imgs ))

fls = glob(dir_new + '/GN2_*_*.sed.png')

ids = unique(array([fl.strip('.sed.png')[-5:] for fl in fls]))

typ = 'sed'
for i, di in enumerate(ids):
    imgs1 = []
    for ii in arange(-1, 4):
        try:
            im1 = PIL.Image.open(dir_new + '/GN2_%i_%s.%s.png'%(ii,di, typ))
        except:
            im1 = PIL.Image.fromarray(zeros((300,1100)), mode = 'RGBA')
        imgs1.append(im1)

    if len(imgs1) > 0:
        min_shape = sorted([(np.sum(i.size), i.size ) for i in imgs1])[0][1]
        imgs_comb1 = np.vstack((np.asarray( i.resize(min_shape) ) for i in imgs1 ))
        imgs_comb_new = PIL.Image.fromarray( imgs_comb1)

        imgs2 = []
        for ii in arange(-1, 4):
            try: im2 = PIL.Image.open(dir_old + '/GN2_%i_%s.%s.png'%(ii,di, typ))
            except: im2 = PIL.Image.fromarray(zeros((300,1100)), mode = 'RGBA')
            imgs2.append(im2)

        if len(imgs2) > 0:
            min_shape = sorted([(np.sum(i.size), i.size ) for i in imgs2])[0][1]
            imgs_comb2 = np.vstack((np.asarray( i.resize(min_shape) ) for i in imgs2 ))
            imgs_comb_old = PIL.Image.fromarray( imgs_comb2)

            imgs = [imgs_comb_old, imgs_comb_new]
            min_shape = sorted([(np.sum(i.size), i.size ) for i in imgs])[0][1]
            imgs_comb = np.hstack((np.asarray( i.resize(min_shape) ) for i in imgs ))
            imgs_comb_all = PIL.Image.fromarray( imgs_comb)

            imgs_comb_all.save(out_dir + '/GN2_%s.%ss.png'%(di, typ))


from drozer.modules.common import loader

class ServiceBinding(loader.ClassLoader):
    """
    Mixin' for binding to a service, and exchanging messages with it.
    """

    class ServiceBindingProxy(object):

        def __init__(self, context, package, component):
            self.context = context
            self.package = package
            self.component = component

            self.bundle = None
            self.binder = None
            self.objFormat = "setData"

        def getData(self):
            return self.binder.printData()

        def getMessage(self):
            return self.binder.getMessage()

        def add_extra(self, extra):
            if self.bundle == None:
                self.bundle = self.context.new("android.os.Bundle")

            if extra[0] == "integer":
                self.bundle.putInt(extra[1], int(extra[2]))
            elif extra[0] == "short":
                self.bundle.putShort(extra[1], int(extra[2]))
            elif extra[0] == "float":
                self.bundle.putFloat(extra[1], float(extra[2]))
            elif extra[0] == "double":
                self.bundle.putDouble(extra[1], float(extra[2]))
            elif extra[0] == "boolean":
                self.bundle.putBoolean(extra[1], extra[2] == "true")
            elif extra[0] == "string":
                self.bundle.putString(extra[1], extra[2])
            elif extra[0] == "byte":
                self.bundle.putByte(extra[1], extra[2])
            elif extra[0] == "char":
                self.bundle.putChar(extra[1], extra[2])
            else:
                raise TypeError

        def setBundle(self, bundle):
            self.bundle = bundle

        def obtain_binder(self):
            if self.binder == None:
                ServiceBinder = self.context.loadClass("common/ServiceBinder.apk", "ServiceBinder")

                self.binder = self.context.new(ServiceBinder)

            return self.binder

        def obtain_message(self, msg):
            return self.context.klass("android.os.Message").obtain(None, int(msg[0]), int(msg[1]), int(msg[2]))

        def setObjFormat(self, format):
            self.objFormat = format

        def send_message(self, msg, timeout):
            binder = self.obtain_binder()
            message = self.obtain_message(msg)

            if self.bundle != None:
                if self.objFormat.upper() == "SETDATA":
                    message.setData(self.bundle)

                if self.objFormat.upper() == "BUNDLEASOBJ":
                    message.obj = self.bundle

            return binder.execute(self.context.getContext(), self.package, self.component, message, int(timeout))

    def getBinding(self, package, component):
        return ServiceBinding.ServiceBindingProxy(self, package, component)


#!/usr/bin/env python3

import argparse
import time
from faker import Faker
from datetime import datetime
import re
import logging

import jsonpickle
from flask import Flask, request, jsonify, Response

from utils import b58encode_int, next_count, LimitedSizeDict, dict_filter

RECORD_LIMIT = 5000

# 6 digit encoded epoch time when the server instance was started
_serial = b58encode_int(int(time.time()))
_card_records = LimitedSizeDict(size_limit=RECORD_LIMIT)
_profile_records = LimitedSizeDict(size_limit=RECORD_LIMIT)
_payment_records = LimitedSizeDict(size_limit=RECORD_LIMIT)

_strict_mode = False
_enable_cache = True

logger = logging.getLogger(__name__)
app = Flask(__name__)

"""
	see https://dev.na.bambora.com/docs/references/payment_APIs/v1-0-5/

	ProfileResponse {
		code : number
		message : string
		customer_code : string
		validation : CardValidation {
			id : string
			approved : integer
			message_id : integer
			message : string
			auth_code : string
			trans_date : string
			order_number : string
			type : string
			amount : number
			cvd_id : integer
		}
	}

	PaymentProfileResponse {
		code : integer
		message : string
		customer_code : string
		status : string
		last_transaction : string
		modified_date : string
		language : string
		velocity_group : string
		profile_group : string
		account_ref : string
		card : GetProfileDefaultCard {
			name : string
			number : string
			expiry_month : string
			expiry_year : string
			card_type : string
		}
		billing : Address {
			name : string
			address_line1 : string
			address_line2 : string
			city : string
			province : string
			country : string
			postal_code : string
			phone_number : string
			email_address : string
		}
		custom : Custom {
			ref1 : string
			ref2 : string
			ref3 : string
			ref4 : string
			ref5 : string
		}
	}

	ErrorResponse {
		code : integer
			category : integer
			message : string
			reference : string
			details : array of Detail {
			field : string
			message : string
		}
		validation : CardValidation {
			id : string
			approved : integer
			message_id : integer
			message : string
			auth_code : string
			trans_date : string
			order_number : string
			type : string
			amount : number
			cvd_id : integer
		}
	}
"""

def _generate_bogus_bamdora_token(prefix, seed):
	# genuine bambora example: "a11-b02846fb-626c-4939-8c73-e26415be8d0d"
	return f"{prefix}-{_serial}-{str(next_count()).zfill(4)}-{datetime.utcnow().strftime('%d%m%Y-%H%M-%S%f')}-{seed[-4:]}"

def _create_new_card_record(data):
	new_card_token = _generate_bogus_bamdora_token('C', data['number'])
	new_card_record = dict(data)
	new_card_record['token'] = new_card_token
	return new_card_record

def _generate_bogus_card_record(token=None):
	cc = Faker().credit_card_full().split('\n')
	card_type = re.split('[!\d]+', cc[0])[0].strip()
	number, expiry = cc[2].split()
	return {
		'token': token or _generate_bogus_bamdora_token('C', number),
		'name': cc[1],
		'number': number,
		'expiry_month': expiry.split('/')[0],
		'expiry_year': expiry.split('/')[1],
		'card_type': card_type,
		'cvc': cc[3].split()[1]
	}

def _generate_empty_card_record(token=None):
	return {
		'token': token or _generate_bogus_bamdora_token('C', '0000000000000000'),
		'name': '',
		'number': '0000000000000000',
		'expiry_month': '12',
		'expiry_year': '99',
		'card_type': ''
	}

def _generate_bogus_billing_address(card_record=None):
	fake = Faker()
	state = fake.state_abbr()
	return {
		'name': card_record['name'] if card_record else fake.name(),
		'address_line1': fake.street_address(),
		'address_line2': '',
		'city': fake.city(),
		'province': state,
		'country': fake.country(),
		'postal_code': fake.zipcode_in_state(state),
		'phone_number': fake.phone_number(),
		'email_address': fake.email()
	}

def _generate_empty_billing_address(card_record=None):
	return {
		'name': card_record['name'] if card_record else '',
		'address_line1': '',
		'address_line2': '',
		'city': '',
		'province': '',
		'country': '',
		'postal_code': '',
		'phone_number': '',
		'email_address': ''
	}

def _create_new_profile_record(customer_code=None, billing_address=None, card_record=None, language=None):
	card_record = card_record or _generate_empty_card_record()
	card_record['card_id'] = 1
	return {
		'customer_code': customer_code or _generate_bogus_bamdora_token('P', card_record['token']),
		'cards': [card_record],
		'billing': billing_address or _generate_empty_billing_address(card_record),
		'status': '',
		'last_transaction': '',
		'modified_date': datetime.utcnow(),
		'language': language or Faker().language_code(),
		'velocity_group': '',
		'profile_group': '',
		'account_ref': ''
	}

def _generate_bogus_profile_record(customer_code=None, card_record=None):
	return _create_new_profile_record(
		customer_code, _generate_bogus_billing_address(), card_record or _generate_bogus_card_record())

@app.route('/scripts/tokenization/tokens', methods=['POST'])
def endpoint_tokenize_card():
	"""
	body parameter:
		TokenRequest {
			number* : string
			expiry_month* : string
			expiry_year* : string
			cvd* : string
		}
	"""
	new_card_record = _create_new_card_record(request.json)
	if _enable_cache:
		_card_records[new_card_record['token']] = new_card_record

	new_card_response = {
		'token': new_card_record['token'],
		'code': 1,
		'version': 1,
		'message': ''
	}
	return jsonify(new_card_response)

@app.route('/v1/profiles', methods=['POST'])
def endpoint_create_profile():
	"""
	body parameter:
		ProfileBody {
			card : ProfileFromCard {
				name* : string
				number* : string
				expiry_month* : string
				expiry_year* : string
				cvd : string
			}
			token : ProfileFromToken {
				name* : string
				code* : string
			}
			billing : Address {
				name : string
				address_line1 : string
				address_line2 : string
				city : string
				province : string
				country : string
				postal_code : string
				phone_number : string
				email_address : string
			}
			custom : Custom {
				ref1 : string
				ref2 : string
				ref3 : string
				ref4 : string
				ref5 : string
			}
			language : string
			comment : string
			validate : boolean
		}

	responses:
		200	The Profile.	ProfileResponse
		400	Bad Request	ErrorResponse
		401	Authentication Failure	ErrorResponse
		402	Business Rule Violation or Decline	ErrorResponse
		403	Authorization Failure	ErrorResponse
		405	Invalid Request Method	ErrorResponse
		500	Internal Server Error	ErrorResponse
	"""

	# profile from card
	if request.json.get('card'):
		card_record = _create_new_card_record(request.json['card'])
		card_token = card_record['token']
	# profile from token
	else:
		card_token = request.json['token']['code']
		card_record = _card_records.get(card_token, None)
		if not card_record:
			if _strict_mode:
				return jsonify({'message': f"unknown card token '{card_token}'"}), 400
			card_record = _generate_empty_card_record(card_token)

	new_profile_record = _create_new_profile_record(
		request.json.get('billing', None), card_record, request.json.get('language', None))

	if _enable_cache:
		_profile_records[new_profile_record['customer_code']] = new_profile_record

	new_profile_response = {
		'code': 1,
		'message': '',
		'customer_code': new_profile_record['customer_code'],
		'validation': {
			'id': '',
			'approved': 1,
			'message_id': 1,
			'message': '',
			'auth_code': '',
			'trans_date': '',
			'order_number': '',
			'type': '',
			'amount': 100,
			'cvd_id': 1
		}
	}
	return jsonify(new_profile_response)

@app.route('/v1/profiles/<profile_id>')
def endpoint_get_profile(profile_id):
	"""
	profile id is a.k.a customer code

	responses:
		200	The Profile.	PaymentProfileResponse
		400	Bad Request	ErrorResponse
		401	Authentication Failure	ErrorResponse
		402	Business Rule Violation or Decline	ErrorResponse
		403	Authorization Failure	ErrorResponse
		405	Invalid Request Method	ErrorResponse
		500	Internal Server Error	ErrorResponse
	"""

	profile_record = _profile_records.get(profile_id, None)
	if not profile_record:
		if _strict_mode:
			return jsonify({'message': f"unknown customer code '{profile_id}'"}), 400
		profile_record = _generate_bogus_profile_record(profile_id)

	response = dict_filter(profile_record, only=[
		'customer_code', 'status', 'last_transaction', 'modified_date', 'language', 'velocity_group', 'profile_group',
		'account_ref', 'billing'])

	response.update({
		'code': 1,
		'message': '',
		'card': profile_record['cards'][0]
	})
	return jsonify(response)

@app.route('/v1/profiles/<profile_id>/cards')
def endpoint_get_profile_cards(profile_id):
	profile_record = _profile_records.get(profile_id, None)
	if not profile_record:
		if _strict_mode:
			return jsonify({'message': f"unknown customer code '{profile_id}'"}), 400
		profile_record = _generate_bogus_profile_record(profile_id)

	cards = []
	for cc in profile_record['cards']:
		cards.append(dict_filter(cc, exclude=['cvc']))

	response = {
		'customer_code': profile_record['customer_code'],
		'code': 1,
		'message': '',
		'card': cards
	}
	return Response(jsonpickle.encode(response))

@app.route('/v1/payments', methods=['POST'])
def endpoint_create_payment():
	try:
		if request.json['payment_method'] != 'payment_profile':
			return jsonify({'message':'can only simulate payment_profile payments'}), 400

		profile_token = request.json['payment_profile']['customer_code']
		profile_record = _profile_records.get(profile_token, None)
		if not profile_record:
			if _strict_mode:
				return jsonify({'message': f"unknown customer code '{profile_token}'"}), 400
			card_record = _create_new_card_record()
			profile_record = _generate_bogus_profile_record(profile_token, card_record)
			if not profile_token:
				profile_token = profile_record['customer_code']
		else:
			try:
				card_record = profile_record['cards'][request.json['payment_profile']['card_id'] - 1]
			except:
				if _strict_mode:
					return jsonify({'message': f"invalid card id '{request.json['payment_profile']['card_id']}'"}), 400
				card_record = _generate_empty_card_record()

		new_payment_id = 10000000 + next_count()
		semi_canned_payment_response = {
			"id": new_payment_id,
			"authorizing_merchant_id": 367410000,
			"approved": "1",
			"message_id": "1",
			"message": "Approved",
			"auth_code": "TEST",
			"created": datetime.utcnow().isoformat(),
			"order_number": "1521750069",
			"type": "P",
			"payment_method": "CC",
			"risk_score": 0,
			"amount": request.json['amount'],
			"custom": {
				"ref1": "",
				"ref2": "",
				"ref3": "",
				"ref4": "",
				"ref5": ""
			},
			"card": {
				"card_type": "VI",
				"last_four": {card_record['number'][-4:]},
				"address_match": 0,
				"postal_result": 0,
				"avs_result": "0",
				"cvd_result": "5",
				"avs": {
					"id": "U",
					"message": "Address information is unavailable.",
					"processed": False
				}
			},
			"links": [
				{
					"rel": "void",
					"href": f"https://api.na.bambora.com/v1/payments/{new_payment_id}/void",
					"method": "POST"
				},
				{
					"rel": "return",
					"href": f"https://api.na.bambora.com/v1/payments/{new_payment_id}/returns",
					"method": "POST"
				}
			]
		}
		new_payment_record = {
			'request': request.json,
			'response': semi_canned_payment_response
		}
		if _enable_cache:
			_payment_records[new_payment_id] = new_payment_record

		# standard json library can't handle nested lists, so pull out the big guns
		json_body = jsonpickle.encode(new_payment_record['response'])
		return Response(json_body)

	except Exception:
		return Response(jsonpickle.encode({
			"id": 123,
			"authorizing_merchant_id": 367410000,
			"approved": "1",
			"message_id": "1",
			"message": "Approved",
			"auth_code": "TEST",
			"created": datetime.utcnow().isoformat(),
			"order_number": "1521750069",
			"type": "P",
			"payment_method": "CC",
			"risk_score": 0,
			"amount": 123,
			"custom": {
				"ref1": "",
				"ref2": "",
				"ref3": "",
				"ref4": "",
				"ref5": ""
			},
			"card": {
				"card_type": "VI",
				"last_four": 1234,
				"address_match": 0,
				"postal_result": 0,
				"avs_result": "0",
				"cvd_result": "5",
				"avs": {
					"id": "U",
					"message": "Address information is unavailable.",
					"processed": False
				}
			},
			"links": [
				{
					"rel": "void",
					"href": f"https://api.na.bambora.com/v1/payments/123/void",
					"method": "POST"
				},
				{
					"rel": "return",
					"href": f"https://api.na.bambora.com/v1/payments/123/returns",
					"method": "POST"
				}
			]
		}))

if __name__ == '__main__':
	logger.setLevel(logging.DEBUG)
	parser = argparse.ArgumentParser()
	parser.add_argument(
		'-s', '--strict', help="enable strict mode which check for valid tokens against cached records", action='store_true')
	parser.add_argument('-n', '--nocache', help="do not cache records", action = 'store_true')
	args = vars(parser.parse_args())
	_strict_mode = args.get('strict', False)
	_enable_cache = not args.get('nocache', False)

	logger.error({'strict_mode': _strict_mode, 'enable_cache': _enable_cache})
	app.run(host='0.0.0.0', port=80)


#
# @lc app=leetcode id=50 lang=python3
#
# [50] Pow(x, n)
#
# https://leetcode.com/problems/powx-n/description/
#
# algorithms
# Medium (30.73%)
# Likes:    1984
# Dislikes: 3499
# Total Accepted:    571.4K
# Total Submissions: 1.9M
# Testcase Example:  '2.00000\n10'
#
# Implement pow(x, n), which calculates x raised to the power n (i.e. x^n).
#
#
# Example 1:
#
#
# Input: x = 2.00000, n = 10
# Output: 1024.00000
#
#
# Example 2:
#
#
# Input: x = 2.10000, n = 3
# Output: 9.26100
#
#
# Example 3:
#
#
# Input: x = 2.00000, n = -2
# Output: 0.25000
# Explanation: 2^-2 = 1/2^2 = 1/4 = 0.25
#
#
#
# Constraints:
#
#
# -100.0 < x < 100.0
# -2^31 <= n <= 2^31-1
# -10^4 <= x^n <= 10^4
#
#
#

# @lc code=start
class Solution:

    def fastPow(self, x: float, n:int) -> float:

        # terminal case handling
        if n == 1:
            return x

        # recursive relation
        res = self.fastPow(x , n // 2)
        res = res * res
        if n % 2 == 1:
            res *= x

        return res

    def myPow(self, x: float, n: int) -> float:
        # edge case
        if n == 0: return 1.0

        # neg n handling
        is_neg_power = n < 0
        n = abs(n)

        # call helper
        res = self.fastPow(x, n)

        # handling neg n
        if is_neg_power:
            res = 1.0 / res
        return res

# @lc code=end


import math
import collections
import time
from functools import reduce
from operator import add
import copy
import os
import sys
from itertools import combinations
from pyspark import SparkContext

# os.environ['PYSPARK_PYTHON'] = '/usr/local/bin/python3.6'
# os.environ['PYSPARK_DRIVER_PYTHON'] = '/usr/local/bin/python3.6'

start       = time.time()
case        = 1
support     = 9
input_path  = "./data/small2.csv"
output_path = "./output.txt"

# case = int(sys.argv[1])
# support = int(sys.argv[2])
# input_path = sys.argv[3]
# output_path = sys.argv[4]

sc = SparkContext ('local[*]','task1')

if case == 1:
    small1  = sc.textFile(input_path, 2).map(lambda l: l.split(",")).filter(lambda l: l[0] != 'user_id').map(lambda l: (l[0], l[1])).distinct()
    baskets = small1.groupByKey().map(lambda s: sorted(list(s[1])))

elif case == 2:
    small1  = sc.textFile(input_path, 2).map(lambda l: l.split(",")).filter(lambda l: l[0] != 'user_id').map(lambda l: (l[1], l[0])).distinct()
    baskets = small1.groupByKey().map(lambda s: sorted(list(s[1])))

total_baskets = baskets.count()
buckets       = 99

#genereate new candidate pairs
def gen_new_cand(freq_cand):

    if len(freq_cand)>0 and freq_cand is not None:

        n        = len(freq_cand)
        si       = len(freq_cand[0])
        new_cand = []

        for i in range(n-1):
            for j in range(i+1,n):
                if freq_cand[i][:-1] == freq_cand[j][:-1]:
                    possible_cand = tuple(sorted(list(set(freq_cand[j]).union(set(freq_cand[i])))))
                    temp = []
                    for subset_item in combinations(possible_cand, si):
                        temp.append(subset_item)
                    if set(temp).issubset(set(freq_cand)):
                        new_cand.append(possible_cand)
                else:
                    break
    return new_cand

#find the frequent itemsets
def find_itemsets(subset_baskets, support, total_baskets):

    temp           = list(subset_baskets)
    ps             = math.ceil(support * len(list(temp)) / total_baskets)
    subset_baskets = temp

    subset_baskets = list(subset_baskets)
    candidates_all = collections.defaultdict(list)

    count_dict = collections.defaultdict(int)
    bit_map    = [0 for i in range(buckets+1)]

    for basket in subset_baskets:
        for item in basket:
            count_dict[item]+=1
        for each_pair in combinations(basket, 2):
            key = (int(list(each_pair)[0]) + int(list(each_pair)[1])) % buckets
            bit_map[key] += 1

    single_freq = sorted([k for k in count_dict.keys() if count_dict[k]>= ps])
    bit_map     = list(map(lambda value: True if value >= ps else False, bit_map))

    item_len = 1
    cand     = single_freq

    candidates_all[str(item_len)] = [tuple(item.split(",")) for item in single_freq]

    while len(cand) > 0 and cand is not None:
        item_len  += 1
        count_dict = collections.defaultdict(int)

        for basket in subset_baskets:
            basket = sorted(list(set(basket).intersection(set(single_freq))))

            if len(basket) >= item_len:
                if item_len == 2:
                    for each_pair in combinations(basket, item_len):
                        key = (int(list(each_pair)[0]) + int(list(each_pair)[1])) % buckets
                        if bit_map[key]:
                            count_dict[each_pair] += 1

                if item_len >= 3:
                    for items in cand:
                        if set(items).issubset(set(basket)):
                            count_dict[items]+=1

        freq_cand = sorted([k for k in count_dict.keys() if count_dict[k] >= ps])

        cand = gen_new_cand(freq_cand)
        if len(freq_cand) == 0:
            break

        candidates_all[str(item_len)] = list(freq_cand)

    yield reduce(lambda val1, val2: val1 + val2, candidates_all.values())

#count frequent itemsets
def count_frequent_itemset(subset_baskets, candidates):
    count_dict = collections.defaultdict(int)
    for pairs in candidates:
        if set(pairs).issubset(set(subset_baskets)):
            count_dict[pairs]+=1

    yield [tuple((k, v)) for k, v in count_dict.items()]

def change_format(items):

    result= ""
    idx = 1

    for item in items:
        if len(item) == 1:
            result += str("(" + str(item)[1:-2] + "),")

        elif len(item) != idx:
            result  = result[:-1]
            result  += "\n\n"
            idx     = len(item)
            result  += (str(item) + ",")
        else:
            result += (str(item) + ",")

    return result[:-1]

candidates = baskets.mapPartitions(lambda part: find_itemsets(subset_baskets = part, support = support, total_baskets = total_baskets)).flatMap(lambda pair: pair).distinct()
sorted_candidates = candidates.sortBy(lambda pair: (len(pair), pair)).collect()
frequent_itemsets = baskets.flatMap(lambda part: count_frequent_itemset(subset_baskets = part,candidates = sorted_candidates)).flatMap(lambda pair: pair).reduceByKey(lambda a,b: a + b).filter(lambda a: a[1] >= support).map(lambda a: a[0])
sorted_frequent_itemsets=frequent_itemsets.sortBy(lambda pair: (len(pair), pair)).collect()

with open(output_path, 'w') as op_file:
    str_result = 'Candidates:\n' + change_format(sorted_candidates) + '\n\n' \
                    + 'Frequent Itemsets:\n' + change_format(sorted_frequent_itemsets)
    op_file.write(str_result)
    op_file.close()

end = time.time()
print("Duration: "+ str(end - start))