from abc import ABCMeta, abstractmethod
import os
from io import BytesIO
import tempfile
from subprocess import Popen, PIPE

# -------------------------------------------------------------------------
#
# Gramps modules
#
# -------------------------------------------------------------------------
from ...const import GRAMPS_LOCALE as glocale

_ = glocale.translation.gettext
from ...utils.file import search_for, where_is
from . import BaseDoc
from ..menu import NumberOption, TextOption, EnumeratedListOption, BooleanOption
from ...constfunc import win

# -------------------------------------------------------------------------
#
# set up logging
#
# -------------------------------------------------------------------------
import logging

LOG = logging.getLogger(".graphdoc")

# -------------------------------------------------------------------------
#
# Private Constants
#
# -------------------------------------------------------------------------
_FONTS = [
    {"name": _("Default"), "value": "serif"},
    {"name": _("PostScript / Helvetica"), "value": "Helvetica"},
    {"name": _("TrueType / FreeSans"), "value": "FreeSans"},
]

_RANKDIR = [
    {"name": _("Vertical (↓)"), "value": "TB"},
    {"name": _("Vertical (↑)"), "value": "BT"},
    {"name": _("Horizontal (→)"), "value": "LR"},
    {"name": _("Horizontal (←)"), "value": "RL"},
]

_NODE_PORTS = {"TB": ("n", "s"), "BT": ("s", "n"), "LR": ("w", "e"), "RL": ("e", "w")}

_PAGEDIR = [
    {"name": _("Bottom, left"), "value": "BL"},
    {"name": _("Bottom, right"), "value": "BR"},
    {"name": _("Top, left"), "value": "TL"},
    {"name": _("Top, Right"), "value": "TR"},
    {"name": _("Right, bottom"), "value": "RB"},
    {"name": _("Right, top"), "value": "RT"},
    {"name": _("Left, bottom"), "value": "LB"},
    {"name": _("Left, top"), "value": "LT"},
]

_RATIO = [
    {"name": _("Compress to minimal size"), "value": "compress"},
    {"name": _("Fill the given area"), "value": "fill"},
    {"name": _("Expand uniformly"), "value": "expand"},
]

_NOTELOC = [{"name": _("Top"), "value": "t"}, {"name": _("Bottom"), "value": "b"}]

_SPLINE = [
    {"name": _("Straight"), "value": "false"},
    {
        "name": _("Curved"),
        "value": "true",
    },
    {"name": _("Orthogonal"), "value": "ortho"},
]

if win():
    _DOT_FOUND = search_for("dot.exe")

    if search_for("gswin32c.exe") == 1:
        _GS_CMD = "gswin32c.exe"
    elif search_for("gswin32.exe") == 1:
        _GS_CMD = "gswin32.exe"
    else:
        _GS_CMD = ""
else:
    _DOT_FOUND = search_for("dot")
    _GS_CMD = where_is("gs")

def esc(id_txt):
    return id_txt.replace('"', '\\"')

# ------------------------------------------------------------------------------
#
# GVOptions
#
# ------------------------------------------------------------------------------
class GVOptions:
    """
    Defines all of the controls necessary
    to configure the graph reports.
    """

    def __init__(self):
        self.h_pages = None
        self.v_pages = None
        self.page_dir = None
        self.dpi = None

    def add_menu_options(self, menu):
        """
        Add all graph related options to the menu.

        :param menu: The menu the options should be added to.
        :type menu: :class:`.Menu`
        :return: nothing
        """
        ################################
        category = _("Graphviz Layout")
        ################################
        font_family = EnumeratedListOption(_("Font family"), "")
        for item in _FONTS:
            font_family.add_item(item["value"], item["name"])
        font_family.set_help(
            _(
                "Choose the font family. If international "
                "characters don't show, use FreeSans font. "
                "FreeSans is available from: "
                "http://www.nongnu.org/freefont/"
            )
        )
        menu.add_option(category, "font_family", font_family)

        font_size = NumberOption(_("Font size"), 14, 8, 128)
        font_size.set_help(_("The font size, in points."))
        menu.add_option(category, "font_size", font_size)

        rank_dir = EnumeratedListOption(_("Graph Direction"), "TB")
        for item in _RANKDIR:
            rank_dir.add_item(item["value"], item["name"])
        rank_dir.set_help(
            _("Whether graph goes from top to bottom " "or left to right.")
        )
        menu.add_option(category, "rank_dir", rank_dir)

        h_pages = NumberOption(_("Number of Horizontal Pages"), 1, 1, 25)
        h_pages.set_help(
            _(
                "Graphviz can create very large graphs by "
                "spreading the graph across a rectangular "
                "array of pages. This controls the number "
                "pages in the array horizontally. "
                "Only valid for dot and pdf via Ghostscript."
            )
        )
        menu.add_option(category, "h_pages", h_pages)

        v_pages = NumberOption(_("Number of Vertical Pages"), 1, 1, 25)
        v_pages.set_help(
            _(
                "Graphviz can create very large graphs by "
                "spreading the graph across a rectangular "
                "array of pages. This controls the number "
                "pages in the array vertically. "
                "Only valid for dot and pdf via Ghostscript."
            )
        )
        menu.add_option(category, "v_pages", v_pages)

        page_dir = EnumeratedListOption(_("Paging Direction"), "BL")
        for item in _PAGEDIR:
            page_dir.add_item(item["value"], item["name"])
        page_dir.set_help(
            _(
                "The order in which the graph pages are output. "
                "This option only applies if the horizontal pages "
                "or vertical pages are greater than 1."
            )
        )
        menu.add_option(category, "page_dir", page_dir)

        spline = EnumeratedListOption(_("Connecting lines"), "true")
        for item in _SPLINE:
            spline.add_item(item["value"], item["name"])
        spline.set_help(_("How the lines between objects will be drawn."))
        menu.add_option(category, "spline", spline)

        node_ports = BooleanOption(_("Alternate line attachment"), False)
        node_ports.set_help(_("Whether lines attach to nodes differently"))
        menu.add_option(category, "node_ports", node_ports)

        # the page direction option only makes sense when the
        # number of horizontal and/or vertical pages is > 1,
        # so we need to remember these 3 controls for later
        self.h_pages = h_pages
        self.v_pages = v_pages
        self.page_dir = page_dir

        # the page direction option only makes sense when the
        # number of horizontal and/or vertical pages is > 1
        self.h_pages.connect("value-changed", self.pages_changed)
        self.v_pages.connect("value-changed", self.pages_changed)

        ################################
        category = _("Graphviz Options")
        ################################

        aspect_ratio = EnumeratedListOption(_("Aspect ratio"), "fill")
        for item in _RATIO:
            aspect_ratio.add_item(item["value"], item["name"])
        help_text = _(
            "Affects node spacing and scaling of the graph.\n"
            "If the graph is smaller than the print area:\n"
            "  Compress will not change the node spacing. \n"
            "  Fill will increase the node spacing to fit the print area in "
            "both width and height.\n"
            "  Expand will increase the node spacing uniformly to preserve "
            "the aspect ratio.\n"
            "If the graph is larger than the print area:\n"
            "  Compress will shrink the graph to achieve tight packing at the "
            "expense of symmetry.\n"
            "  Fill will shrink the graph to fit the print area after first "
            "increasing the node spacing.\n"
            "  Expand will shrink the graph uniformly to fit the print area."
        )
        aspect_ratio.set_help(help_text)
        menu.add_option(category, "ratio", aspect_ratio)

        dpi = NumberOption(_("DPI"), 75, 20, 1200)
        dpi.set_help(
            _(
                "Dots per inch.  When creating images such as "
                ".gif or .png files for the web, try numbers "
                "such as 100 or 300 DPI.  PostScript and PDF files "
                "always use 72 DPI."
            )
        )
        menu.add_option(category, "dpi", dpi)
        self.dpi = dpi

        nodesep = NumberOption(_("Node spacing"), 0.20, 0.01, 5.00, 0.01)
        nodesep.set_help(
            _(
                "The minimum amount of free space, in inches, "
                "between individual nodes.  For vertical graphs, "
                "this corresponds to spacing between columns.  "
                "For horizontal graphs, this corresponds to "
                "spacing between rows."
            )
        )
        menu.add_option(category, "nodesep", nodesep)

        ranksep = NumberOption(_("Rank spacing"), 0.20, 0.01, 5.00, 0.01)
        ranksep.set_help(
            _(
                "The minimum amount of free space, in inches, "
                "between ranks.  For vertical graphs, this "
                "corresponds to spacing between rows.  For "
                "horizontal graphs, this corresponds to spacing "
                "between columns."
            )
        )
        menu.add_option(category, "ranksep", ranksep)

        ################################
        category = _("Note")
        ################################

        note = TextOption(_("Note to add to the graph"), [""])
        note.set_help(_("This text will be added to the graph."))
        menu.add_option(category, "note", note)

        noteloc = EnumeratedListOption(_("Note location"), "t")
        for i in range(0, len(_NOTELOC)):
            noteloc.add_item(_NOTELOC[i]["value"], _NOTELOC[i]["name"])
        noteloc.set_help(_("Whether note will appear on top " "or bottom of the page."))
        menu.add_option(category, "noteloc", noteloc)

        notesize = NumberOption(_("Note size"), 32, 8, 128)
        notesize.set_help(_("The size of note text, in points."))
        menu.add_option(category, "notesize", notesize)

    def pages_changed(self):
        """
        This method gets called every time the v_pages or h_pages
        options are changed; when both vertical and horizontal
        pages are set to "1", then the page_dir control needs to
        be unavailable
        """
        if self.v_pages.get_value() > 1 or self.h_pages.get_value() > 1:
            self.page_dir.set_available(True)
        else:
            self.page_dir.set_available(False)

# ------------------------------------------------------------------------------
#
# GVDoc
#
# ------------------------------------------------------------------------------
class GVDoc(metaclass=ABCMeta):
    """
    Abstract Interface for Graphviz document generators. Output formats
    for Graphviz reports must implement this interface to be used by the
    report system.
    """

    @abstractmethod
    def add_node(
        self,
        node_id,
        label,
        shape="",
        color="",
        style="",
        fillcolor="",
        url="",
        htmloutput=False,
    ):
        """
        Add a node to this graph. Nodes can be different shapes like boxes and
        circles.

        :param node_id: A unique identification value for this node.
            Example: "p55"
        :type node_id: string
        :param label: The text to be displayed in the node.
            Example: "John Smith"
        :type label: string
        :param shape: The shape for the node.
            Examples: "box", "ellipse", "circle"
        :type shape: string
        :param color: The color of the node line.
            Examples: "blue", "lightyellow"
        :type color: string
        :param style: The style of the node.
        :type style: string
        :param fillcolor: The fill color for the node.
            Examples: "blue", "lightyellow"
        :type fillcolor: string
        :param url: A URL for the node.
        :type url: string
        :param htmloutput: Whether the label contains HTML.
        :type htmloutput: boolean
        :return: nothing
        """

    @abstractmethod
    def add_link(self, id1, id2, style="", head="", tail="", comment=""):
        """
        Add a link between two nodes.

        :param id1: The unique identifier of the starting node.
            Example: "p55"
        :type id1: string
        :param id2: The unique identifier of the ending node.
            Example: "p55"
        :type id2: string
        :param comment: A text string displayed at the end of the link line.
            Example: "person C is the son of person A and person B"
        :type comment: string
        :return: nothing
        """

    @abstractmethod
    def add_comment(self, comment):
        """
        Add a comment to the source file.

        :param comment: A text string to add as a comment.
            Example: "Next comes the individuals."
        :type comment: string
        :return: nothing
        """

    @abstractmethod
    def add_samerank(self, id1, id2):
        """
        Force the same line for two linked nodes.

        :param id1: The unique identifier of the starting node.
            Example: "p55"
        :type id1: string
        :param id2: The unique identifier of the ending node.
            Example: "p55"
        :type id2: string
        :return: nothing
        """

    @abstractmethod
    def rewrite_label(self, id, label):
        """
        Rewrite the node label.

        :param id: The unique identifier of the node.
            Example: "p55"
        :type id: string
        :param label: The text to be displayed in the node.
            Example: "John Smith"
        :type label: string
        :return: nothing
        """

    @abstractmethod
    def start_subgraph(self, graph_id):
        """
        Start a subgraph in this graph.

        :param id: The unique identifier of the subgraph.
            Example: "p55"
        :type id1: string
        :return: nothing
        """

    @abstractmethod
    def end_subgraph(self):
        """
        End a subgraph that was previously started in this graph.

        :return: nothing
        """

# ------------------------------------------------------------------------------
#
# GVDocBase
#
# ------------------------------------------------------------------------------
class GVDocBase(BaseDoc, GVDoc):
    """
    Base document generator for all Graphviz document generators. Classes that
    inherit from this class will only need to implement the close function.
    The close function will generate the actual file of the appropriate type.
    """

    def __init__(self, options, paper_style, uistate=None):
        BaseDoc.__init__(self, None, paper_style, uistate=uistate)

        self._filename = None
        self._dot = BytesIO()
        self._paper = paper_style

        get_option = options.menu.get_option_by_name

        self.dpi = get_option("dpi").get_value()
        self.fontfamily = get_option("font_family").get_value()
        self.fontsize = get_option("font_size").get_value()
        self.hpages = get_option("h_pages").get_value()
        self.nodesep = get_option("nodesep").get_value()
        self.noteloc = get_option("noteloc").get_value()
        self.notesize = get_option("notesize").get_value()
        self.note = get_option("note").get_value()
        self.pagedir = get_option("page_dir").get_value()
        self.rankdir = get_option("rank_dir").get_value()
        self.ranksep = get_option("ranksep").get_value()
        self.ratio = get_option("ratio").get_value()
        self.vpages = get_option("v_pages").get_value()
        self.spline = get_option("spline").get_value()
        self.node_ports = get_option("node_ports").get_value()

        paper_size = paper_style.get_size()

        # Subtract 0.01" from the drawing area to make some room between
        # this area and the margin in order to compensate for different
        # rounding errors internally in dot
        sizew = (
            paper_size.get_width()
            - self._paper.get_left_margin()
            - self._paper.get_right_margin()
        ) / 2.54 - 0.01
        sizeh = (
            paper_size.get_height()
            - self._paper.get_top_margin()
            - self._paper.get_bottom_margin()
        ) / 2.54 - 0.01

        pheight = paper_size.get_height_inches()
        pwidth = paper_size.get_width_inches()

        xmargin = self._paper.get_left_margin() / 2.54
        ymargin = self._paper.get_top_margin() / 2.54

        sizew *= self.hpages
        sizeh *= self.vpages

        self.write(
            "digraph GRAMPS_graph\n"
            "{\n"
            "  bgcolor=white;\n"
            '  center="true"; \n'
            '  charset="utf8";\n'
            '  concentrate="false";\n'
            + '  dpi="%d";\n' % self.dpi
            + "  graph [fontsize=%d];\n" % self.fontsize
            + '  margin="%3.2f,%3.2f"; \n' % (xmargin, ymargin)
            + '  mclimit="99";\n'
            + '  nodesep="%.2f";\n' % self.nodesep
            + '  outputorder="edgesfirst";\n'
            + ("#" if self.hpages == self.vpages == 1 else "")
            +
            # comment out "page=" if the graph is on 1 page (bug #2121)
            '  page="%3.2f,%3.2f";\n' % (pwidth, pheight)
            + '  pagedir="%s";\n' % self.pagedir
            + '  rankdir="%s";\n' % self.rankdir
            + '  ranksep="%.2f";\n' % self.ranksep
            + '  ratio="%s";\n' % self.ratio
            + '  searchsize="100";\n'
            + '  size="%3.2f,%3.2f"; \n' % (sizew, sizeh)
            + '  splines="%s";\n' % self.spline
            + "\n"
            + "  edge [len=0.5 style=solid fontsize=%d];\n" % self.fontsize
        )
        if self.node_ports:
            self.write(
                "  edge [headport=%s tailport=%s];\n" % _NODE_PORTS[self.rankdir]
            )

        if self.fontfamily:
            self.write(
                '  node [style=filled fontname="%s" fontsize=%d];\n'
                % (self.fontfamily, self.fontsize)
            )
        else:
            self.write("  node [style=filled fontsize=%d];\n" % self.fontsize)
        self.write("\n")

    def write(self, text):
        """Write text to the dot file"""
        self._dot.write(text.encode("utf8", "xmlcharrefreplace"))

    def open(self, filename):
        """Implement GVDocBase.open()"""
        self._filename = os.path.normpath(os.path.abspath(filename))

    def close(self):
        """
        This isn't useful by itself. Other classes need to override this and
        actually generate a file.
        """
        if self.note:
            # build up the label
            label = ""
            for line in self.note:  # for every line in the note...
                line = line.strip()  # ...strip whitespace from this line...
                if line != "":  # ...and if we still have a line...
                    if label != "":  # ...see if we need to insert a newline...
                        label += "\\n"
                    label += line.replace('"', '\\"')

            # after all that, see if we have a label to display
            if label != "":
                self.write(
                    "\n"
                    + '  label="%s";\n' % label
                    + '  labelloc="%s";\n' % self.noteloc
                    + '  fontsize="%d";\n' % self.notesize
                )

        self.write("}\n\n")

    def add_node(
        self,
        node_id,
        label,
        shape="",
        color="",
        style="",
        fillcolor="",
        url="",
        htmloutput=False,
    ):
        """
        Add a node to this graph. Nodes can be different shapes like boxes and
        circles.

        Implements GVDocBase.add_node().
        """
        text = "["

        if shape:
            text += ' shape="%s"' % shape

        if color:
            text += ' color="%s"' % color

        if fillcolor:
            text += ' fillcolor="%s"' % fillcolor

        if style:
            text += ' style="%s"' % style

        # note that we always output a label -- even if an empty string --
        # otherwise Graphviz uses the node ID as the label which is unlikely
        # to be what the user wants to see in the graph
        if label.startswith("<") or htmloutput:
            text += " label=<%s>" % label
        else:
            text += ' label="%s"' % label

        if url:
            text += ' URL="%s"' % url

        text += " ]"
        self.write('  "%s" %s;\n' % (esc(node_id), text))

    def add_link(self, id1, id2, style="", head="", tail="", comment=""):
        """
        Add a link between two nodes.

        Implements GVDocBase.add_link().
        """
        self.write('  "%s" -> "%s"' % (esc(id1), esc(id2)))

        if style or head or tail:
            self.write(" [")

            if style:
                self.write(" style=%s" % style)
            if head:
                self.write(" arrowhead=%s" % head)
            if tail:
                self.write(" arrowtail=%s" % tail)
            if head:
                if tail:
                    self.write(" dir=both")
                else:
                    self.write(" dir=forward")
            else:
                if tail:
                    self.write(" dir=back")
                else:
                    self.write(" dir=none")
            self.write(" ]")

        self.write(";")

        if comment:
            self.write(" // %s" % comment)

        self.write("\n")

    def add_comment(self, comment):
        """
        Add a comment.

        Implements GVDocBase.add_comment().
        """
        tmp = comment.split("\n")
        for line in tmp:
            text = line.strip()
            if text == "":
                self.write("\n")
            elif text.startswith("#"):
                self.write("%s\n" % text)
            else:
                self.write("# %s\n" % text)

    def add_samerank(self, id1, id2):
        """
        Force the same line for two linked nodes.

        Implements GVDocBase.add_samerank().
        """
        self.write('  {rank=same "%s" "%s"}\n' % (esc(id1), esc(id2)))

    def rewrite_label(self, id, label):
        """
        Rewrite the node label.

        Implements GVDocBase.rewrite_label().
        """
        self.write('  "%s" [label = "%s"]\n' % (esc(id), label))

    def start_subgraph(self, graph_id):
        """Implement GVDocBase.start_subgraph()"""
        graph_id = graph_id.replace(" ", "_")  # for user-defined ID with space
        self.write(
            "  subgraph cluster_%s\n" % graph_id + "  {\n" + '  style="invis";\n'
        )  # no border around subgraph (#0002176)

    def end_subgraph(self):
        """Implement GVDocBase.end_subgraph()"""
        self.write("  }\n")

# ------------------------------------------------------------------------------
#
# GVDotDoc
#
# ------------------------------------------------------------------------------
class GVDotDoc(GVDocBase):
    """GVDoc implementation that generates a .gv text file."""

    def close(self):
        """Implements GVDotDoc.close()"""
        GVDocBase.close(self)

        # Make sure the extension is correct
        if self._filename[-3:] != ".gv":
            self._filename += ".gv"

        with open(self._filename, "wb") as dotfile:
            dotfile.write(self._dot.getvalue())

# ------------------------------------------------------------------------------
#
# GVPsDoc
#
# ------------------------------------------------------------------------------
class GVPsDoc(GVDocBase):
    """GVDoc implementation that generates a .ps file using Graphviz."""

    def __init__(self, options, paper_style):
        # DPI must always be 72 for PDF.
        # GV documentation says dpi is only for image formats.
        options.menu.get_option_by_name("dpi").set_value(72)
        GVDocBase.__init__(self, options, paper_style)
        # GV documentation allow multiple pages only for ps format,
        # But it does not work with -Tps:cairo in order to
        # show Non Latin-1 letters. Force to only 1 page.
        # See bug tracker issue 2815
        options.menu.get_option_by_name("v_pages").set_value(1)
        options.menu.get_option_by_name("h_pages").set_value(1)
        GVDocBase.__init__(self, options, paper_style)

    def close(self):
        """Implements GVPsDoc.close()"""
        GVDocBase.close(self)

        # Make sure the extension is correct
        if self._filename[-3:] != ".ps":
            self._filename += ".ps"

        # Create a temporary dot file
        (handle, tmp_dot) = tempfile.mkstemp(".gv")
        dotfile = os.fdopen(handle, "wb")
        dotfile.write(self._dot.getvalue())
        dotfile.close()

        # Generate the PS file.
        # Reason for using -Tps:cairo. Needed for Non Latin-1 letters
        # Some testing with Tps:cairo. Non Latin-1 letters are OK i all cases:
        # Output format:   ps       PDF-GostScript  PDF-Graphviz
        # Single page      OK           OK              OK
        # Multip page    1 page,        OK             1 page,
        #               corrupted                    set by gramps
        # If I take a correct multip page PDF and convert it with pdf2ps I get
        # multip pages, but the output is clipped, some margins have
        # disappeared. I used 1 inch margins always.
        # See bug tracker issue 2815
        # :cairo does not work with Graphviz 2.26.3 and later See issue 4164
        # recent versions of Graphviz doesn't even try, just puts out a single
        # large page.

        command = 'dot -Tps:cairo -o"%s" "%s"' % (self._filename, tmp_dot)
        dotversion = str(Popen(["dot", "-V"], stderr=PIPE).communicate(input=None)[1])
        # Problem with dot 2.26.3 and later and multiple pages, which gives
        # "cairo: out of memory" If the :cairo is skipped for these cases it
        # gives bad result for non-Latin-1 characters (utf-8).
        if (dotversion.find("2.26.3") or dotversion.find("2.28.0") != -1) and (
            self.vpages * self.hpages
        ) > 1:
            command = command.replace(":cairo", "")
        os.system(command)
        # Delete the temporary dot file
        os.remove(tmp_dot)

# ------------------------------------------------------------------------------
#
# GVSvgDoc
#
# ------------------------------------------------------------------------------
class GVSvgDoc(GVDocBase):
    """GVDoc implementation that generates a .svg file using Graphviz."""

    def __init__(self, options, paper_style):
        # GV documentation allow multiple pages only for ps format,
        # which also includes pdf via ghostscript.
        options.menu.get_option_by_name("v_pages").set_value(1)
        options.menu.get_option_by_name("h_pages").set_value(1)
        GVDocBase.__init__(self, options, paper_style)

    def close(self):
        """Implements GVSvgDoc.close()"""
        GVDocBase.close(self)

        # Make sure the extension is correct
        if self._filename[-4:] != ".svg":
            self._filename += ".svg"

        # Create a temporary dot file
        (handle, tmp_dot) = tempfile.mkstemp(".gv")
        dotfile = os.fdopen(handle, "wb")
        dotfile.write(self._dot.getvalue())
        dotfile.close()
        # Generate the SVG file.
        os.system('dot -Tsvg:cairo -o"%s" "%s"' % (self._filename, tmp_dot))

        # Delete the temporary dot file
        os.remove(tmp_dot)

# ------------------------------------------------------------------------------
#
# GVSvgzDoc
#
# ------------------------------------------------------------------------------
class GVSvgzDoc(GVDocBase):
    """GVDoc implementation that generates a .svg file using Graphviz."""

    def __init__(self, options, paper_style):
        # GV documentation allow multiple pages only for ps format,
        # which also includes pdf via ghostscript.
        options.menu.get_option_by_name("v_pages").set_value(1)
        options.menu.get_option_by_name("h_pages").set_value(1)
        GVDocBase.__init__(self, options, paper_style)

    def close(self):
        """Implements GVSvgzDoc.close()"""
        GVDocBase.close(self)

        # Make sure the extension is correct
        if self._filename[-5:] != ".svgz":
            self._filename += ".svgz"

        # Create a temporary dot file
        (handle, tmp_dot) = tempfile.mkstemp(".gv")
        dotfile = os.fdopen(handle, "wb")
        dotfile.write(self._dot.getvalue())
        dotfile.close()
        # Generate the SVGZ file.
        os.system('dot -Tsvgz -o"%s" "%s"' % (self._filename, tmp_dot))

        # Delete the temporary dot file
        os.remove(tmp_dot)

# ------------------------------------------------------------------------------
#
# GVPngDoc
#
# ------------------------------------------------------------------------------
class GVPngDoc(GVDocBase):
    """GVDoc implementation that generates a .png file using Graphviz."""

    def __init__(self, options, paper_style):
        # GV documentation allow multiple pages only for ps format,
        # which also includes pdf via ghostscript.
        options.menu.get_option_by_name("v_pages").set_value(1)
        options.menu.get_option_by_name("h_pages").set_value(1)
        GVDocBase.__init__(self, options, paper_style)

    def close(self):
        """Implements GVPngDoc.close()"""
        GVDocBase.close(self)

        # Make sure the extension is correct
        if self._filename[-4:] != ".png":
            self._filename += ".png"

        # Create a temporary dot file
        (handle, tmp_dot) = tempfile.mkstemp(".gv")
        dotfile = os.fdopen(handle, "wb")
        dotfile.write(self._dot.getvalue())
        dotfile.close()
        # Generate the PNG file.
        os.system('dot -Tpng -o"%s" "%s"' % (self._filename, tmp_dot))

        # Delete the temporary dot file
        os.remove(tmp_dot)

# ------------------------------------------------------------------------------
#
# GVJpegDoc
#
# ------------------------------------------------------------------------------
class GVJpegDoc(GVDocBase):
    """GVDoc implementation that generates a .jpg file using Graphviz."""

    def __init__(self, options, paper_style):
        # GV documentation allow multiple pages only for ps format,
        # which also includes pdf via ghostscript.
        options.menu.get_option_by_name("v_pages").set_value(1)
        options.menu.get_option_by_name("h_pages").set_value(1)
        GVDocBase.__init__(self, options, paper_style)

    def close(self):
        """Implements GVJpegDoc.close()"""
        GVDocBase.close(self)

        # Make sure the extension is correct
        if self._filename[-4:] != ".jpg":
            self._filename += ".jpg"

        # Create a temporary dot file
        (handle, tmp_dot) = tempfile.mkstemp(".gv")
        dotfile = os.fdopen(handle, "wb")
        dotfile.write(self._dot.getvalue())
        dotfile.close()
        # Generate the JPEG file.
        os.system('dot -Tjpg -o"%s" "%s"' % (self._filename, tmp_dot))

        # Delete the temporary dot file
        os.remove(tmp_dot)

# ------------------------------------------------------------------------------
#
# GVGifDoc
#
# ------------------------------------------------------------------------------
class GVGifDoc(GVDocBase):
    """GVDoc implementation that generates a .gif file using Graphviz."""

    def __init__(self, options, paper_style):
        # GV documentation allow multiple pages only for ps format,
        # which also includes pdf via ghostscript.
        options.menu.get_option_by_name("v_pages").set_value(1)
        options.menu.get_option_by_name("h_pages").set_value(1)
        GVDocBase.__init__(self, options, paper_style)

    def close(self):
        """Implements GVGifDoc.close()"""
        GVDocBase.close(self)

        # Make sure the extension is correct
        if self._filename[-4:] != ".gif":
            self._filename += ".gif"

        # Create a temporary dot file
        (handle, tmp_dot) = tempfile.mkstemp(".gv")
        dotfile = os.fdopen(handle, "wb")
        dotfile.write(self._dot.getvalue())
        dotfile.close()
        # Generate the GIF file.
        os.system('dot -Tgif -o"%s" "%s"' % (self._filename, tmp_dot))

        # Delete the temporary dot file
        os.remove(tmp_dot)

# ------------------------------------------------------------------------------
#
# GVPdfGvDoc
#
# ------------------------------------------------------------------------------
class GVPdfGvDoc(GVDocBase):
    """GVDoc implementation that generates a .pdf file using Graphviz."""

    def __init__(self, options, paper_style):
        # DPI must always be 72 for PDF.
        # GV documentation says dpi is only for image formats.
        options.menu.get_option_by_name("dpi").set_value(72)
        # GV documentation allow multiple pages only for ps format,
        # which also includes pdf via ghostscript.
        options.menu.get_option_by_name("v_pages").set_value(1)
        options.menu.get_option_by_name("h_pages").set_value(1)
        GVDocBase.__init__(self, options, paper_style)

    def close(self):
        """Implements GVPdfGvDoc.close()"""
        GVDocBase.close(self)

        # Make sure the extension is correct
        if self._filename[-4:] != ".pdf":
            self._filename += ".pdf"

        # Create a temporary dot file
        (handle, tmp_dot) = tempfile.mkstemp(".gv")
        dotfile = os.fdopen(handle, "wb")
        dotfile.write(self._dot.getvalue())
        dotfile.close()
        fname = self._filename

        # Generate the PDF file.
        os.system('dot -Tpdf -o"%s" "%s"' % (fname, tmp_dot))

        # Delete the temporary dot file
        os.remove(tmp_dot)

# ------------------------------------------------------------------------------
#
# GVPdfGsDoc
#
# ------------------------------------------------------------------------------
class GVPdfGsDoc(GVDocBase):
    """GVDoc implementation that generates a .pdf file using Ghostscript."""

    def __init__(self, options, paper_style):
        # DPI must always be 72 for PDF.
        # GV documentation says dpi is only for image formats.
        options.menu.get_option_by_name("dpi").set_value(72)
        GVDocBase.__init__(self, options, paper_style)

    def close(self):
        """Implements GVPdfGsDoc.close()"""
        GVDocBase.close(self)

        # Make sure the extension is correct
        if self._filename[-4:] != ".pdf":
            self._filename += ".pdf"

        # Create a temporary dot file
        (handle, tmp_dot) = tempfile.mkstemp(".gv")
        dotfile = os.fdopen(handle, "wb")
        dotfile.write(self._dot.getvalue())
        dotfile.close()

        # Create a temporary PostScript file
        (handle, tmp_ps) = tempfile.mkstemp(".ps")
        os.close(handle)

        # Generate PostScript using dot
        # Reason for using -Tps:cairo. Needed for Non Latin-1 letters
        # See bug tracker issue 2815
        # :cairo does not work with with multi-page See issue 4164
        # recent versions of Graphviz doesn't even try, just puts out a single
        # large page, so we use Ghostscript to split it up.

        command = 'dot -Tps:cairo -o"%s" "%s"' % (tmp_ps, tmp_dot)
        os.system(command)

        # Add .5 to remove rounding errors.
        paper_size = self._paper.get_size()
        width_pt = int((paper_size.get_width_inches() * 72) + 0.5)
        height_pt = int((paper_size.get_height_inches() * 72) + 0.5)
        if (self.vpages * self.hpages) == 1:
            # -dDEVICEWIDTHPOINTS=%d' -dDEVICEHEIGHTPOINTS=%d
            command = (
                "%s -q -sDEVICE=pdfwrite -dNOPAUSE "
                "-dDEVICEWIDTHPOINTS=%d -dDEVICEHEIGHTPOINTS=%d "
                '-sOutputFile="%s" "%s" -c quit'
                % (_GS_CMD, width_pt, height_pt, self._filename, tmp_ps)
            )
            os.system(command)
            os.remove(tmp_ps)
            return
        # Margins (in centimeters) to pixels 72/2.54=28.345
        margin_t = int(28.345 * self._paper.get_top_margin())
        margin_b = int(28.345 * self._paper.get_bottom_margin())
        margin_r = int(28.345 * self._paper.get_right_margin())
        margin_l = int(28.345 * self._paper.get_left_margin())
        margin_x = margin_l + margin_r
        margin_y = margin_t + margin_b
        # Convert to PDF using ghostscript
        list_of_pieces = []

        x_rng = (
            range(1, self.hpages + 1)
            if "L" in self.pagedir
            else range(self.hpages, 0, -1)
        )
        y_rng = (
            range(1, self.vpages + 1)
            if "B" in self.pagedir
            else range(self.vpages, 0, -1)
        )
        if self.pagedir[0] in "TB":
            the_list = ((__x, __y) for __y in y_rng for __x in x_rng)
        else:
            the_list = ((__x, __y) for __x in x_rng for __y in y_rng)
        for __x, __y in the_list:
            # Slit PS file to pieces of PDF
            page_offset_x = (__x - 1) * (margin_x - width_pt)
            page_offset_y = (__y - 1) * (margin_y - height_pt)
            tmp_pdf_piece = "%s_%d_%d.pdf" % (tmp_ps, __x, __y)
            list_of_pieces.append(tmp_pdf_piece)
            # Generate Ghostscript code
            command = (
                "%s -q -dBATCH -dNOPAUSE -dSAFER "
                "-dDEVICEWIDTHPOINTS=%d -dDEVICEHEIGHTPOINTS=%d "
                '-dFIXEDMEDIA -sOutputFile="%s" -sDEVICE=pdfwrite '
                '-c "<</.HWMargins [%d %d %d %d] /PageOffset [%d %d]>> '
                'setpagedevice" -f "%s"'
                % (
                    _GS_CMD,
                    width_pt + 10,
                    height_pt + 10,
                    tmp_pdf_piece,
                    margin_l,
                    margin_b,
                    margin_r,
                    margin_t,
                    page_offset_x + 5,
                    page_offset_y + 5,
                    tmp_ps,
                )
            )
            # Execute Ghostscript
            os.system(command)
        # Merge pieces to single multipage PDF ;
        command = (
            "%s -q -dBATCH -dNOPAUSE "
            '-sOUTPUTFILE="%s" -sDEVICE=pdfwrite %s '
            % (_GS_CMD, self._filename, " ".join(list_of_pieces))
        )
        os.system(command)

        # Clean temporary files
        os.remove(tmp_ps)
        for tmp_pdf_piece in list_of_pieces:
            os.remove(tmp_pdf_piece)
        os.remove(tmp_dot)

# ------------------------------------------------------------------------------
#
# Various Graphviz formats.
#
# ------------------------------------------------------------------------------
FORMATS = []

if _DOT_FOUND:
    if _GS_CMD != "":
        FORMATS += [
            {
                "type": "gspdf",
                "ext": "pdf",
                "descr": _("PDF (Ghostscript)"),
                "mime": "application/pdf",
                "class": GVPdfGsDoc,
            }
        ]

    FORMATS += [
        {
            "type": "gvpdf",
            "ext": "pdf",
            "descr": _("PDF (Graphviz)"),
            "mime": "application/pdf",
            "class": GVPdfGvDoc,
        }
    ]

    FORMATS += [
        {
            "type": "ps",
            "ext": "ps",
            "descr": _("PostScript"),
            "mime": "application/postscript",
            "class": GVPsDoc,
        }
    ]

    FORMATS += [
        {
            "type": "svg",
            "ext": "svg",
            "descr": _("Structured Vector Graphics (SVG)"),
            "mime": "image/svg",
            "class": GVSvgDoc,
        }
    ]

    FORMATS += [
        {
            "type": "svgz",
            "ext": "svgz",
            "descr": _("Compressed Structured Vector Graphs (SVGZ)"),
            "mime": "image/svgz",
            "class": GVSvgzDoc,
        }
    ]

    FORMATS += [
        {
            "type": "jpg",
            "ext": "jpg",
            "descr": _("JPEG image"),
            "mime": "image/jpeg",
            "class": GVJpegDoc,
        }
    ]

    FORMATS += [
        {
            "type": "gif",
            "ext": "gif",
            "descr": _("GIF image"),
            "mime": "image/gif",
            "class": GVGifDoc,
        }
    ]

    FORMATS += [
        {
            "type": "png",
            "ext": "png",
            "descr": _("PNG image"),
            "mime": "image/png",
            "class": GVPngDoc,
        }
    ]

FORMATS += [
    {
        "type": "dot",
        "ext": "gv",
        "descr": _("Graphviz File"),
        "mime": "text/x-graphviz",
        "class": GVDotDoc,
    }
]


# -*- coding: utf-8 -*-

"""DataMine (Upsight.com) CLI Admin Tool

usedup is a simple command line tool for performing basic admin tasks
using both official and unofficial DataMine api's. At present this allows
for; checking currently running queries, viewing a running queries stats,
killing active queries, and/or downloading an archive of historically run
queries. Since login is required to access DataMine(Upsight.com), user
credentials will be asked upon submitting each command.

USAGE:
    $ usedup --help

See the README for further details.

"""
from __future__ import print_function

import time

import click
from tabulate import tabulate

from usedup.core import UsedUp

@click.group()
def main():
    """
    DataMine (Upsight.com) CLI Admin Tool

    usedup is a simple command line tool for performing basic admin tasks
    using both official and unofficial DataMine api's. At present this allows
    for; checking currently running queries, viewing a running queries stats,
    killing active queries, and/or downloading an archive of historically run
    queries. Since login is required to access DataMine(Upsight.com), user
    credentials will be asked upon submitting each command.

    USAGE:
        $ usedup --help

    See the README for further details.

    """
    pass

@main.command()
@click.option('--period', '-p', default='1wk', help='DataMine periods back from now.')
@click.option('--outfile', '-o', help='Output file save location (.csv)')
def history(period, outfile):
    """Download an archive of previously run queries.

    Period Options:
    1wk=='1 week';
    2wk=='2 weeks';
    1mt=='1 month';
    2mt=='2 months';
    3mt=='3 months';

    Output File: Saves to current path if missing.

    """
    with UsedUp() as usedup:
        usedup.history(period=period, outfile=outfile)

@main.command()
@click.argument('qids', nargs=-1)
def kill(qids):
    """Kill actively running query using Upsight's Query ID."""
    with UsedUp() as usedup:
        for qid in qids:
            result = usedup.kill(qid=qid)
            print('QueryID %s canceled successfully? %s' % (qid, result))

@main.command()
@click.option('--continuous', '-c', is_flag=True, help='Run indefinately.')
@click.option('--pause', '-p', default=60, help='Seconds between checks.')
def running(continuous, pause):
    """Print list of currently running queries."""
    showresults = True
    with UsedUp() as usedup:
        while showresults:
            queries = usedup.running()
            tabbed = tabulate(queries, headers='keys', tablefmt="grid")
            print(tabbed)
            showresults = continuous
            if showresults:
                time.sleep(pause)

@main.command()
@click.argument('qids', nargs=-1)
@click.option('--details/--no-details', default=True, help='Show processing details?')
@click.option('--query/--no-query', default=True, help='Show query statement?')
def status(qids, details, query):
    """Print details about active query Upsight's Query ID."""
    with UsedUp() as usedup:
        for qid in qids:
            result = usedup.status(qid, details, query)

            if result is None:
                print('QueryID %s is not an active query.' % qid)

            if details and result['details'] is not None:
                sections = sorted(result['details'].keys())
                for section in sections:
                    print('%s == %s' % (section, result['details'][section]))

            if query and result['query'] is not None:
                print('%s == \n\n%s' % ('Query', result['query']))

if __name__ == '__main__':
    main()


from typing import List, Union, Any

from random import random
import numpy as np

import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cf
import geopandas as gpd

from shapely.geometry import MultiPolygon, Polygon

def display_polygons(polygons_list: List[Union[Polygon, MultiPolygon]], fill=True, show=True) -> Any:
    """
    Display in different colours a set of polygons or multipolygons.

    Parameters
    ----------
    polygons_list: List[Union[Polygon, MultiPolygon]]
        List of shapely polygons or multipolygons
    fill: bool (default: True)
        Whether to fill in shapes or not
    show: bool (default: True)
        Whether to use plt.show or to return plotting axis
    """

    assert isinstance(polygons_list, list) or isinstance(polygons_list, np.ndarray) \
        or isinstance(polygons_list, gpd.array.GeometryArray), \
        f'The argument must be a list of polygons or multipolygons, got {type(polygons_list)}'

    land_50m = cf.NaturalEarthFeature('physical', 'land', '50m',
                                      edgecolor='darkgrey',
                                      facecolor=cf.COLORS['land_alt1'])

    fig = plt.figure(figsize=(13, 13))
    ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
    ax.add_feature(land_50m, linewidth=0.5, zorder=-1)
    ax.add_feature(cf.BORDERS.with_scale('50m'), edgecolor='darkgrey', linewidth=0.5, zorder=-1)

    from matplotlib import cm
    cmap = cm.get_cmap('viridis')

    norm = np.linspace(0.3, 1, len(polygons_list))
    for i, polygons in enumerate(polygons_list):
        c = cmap(norm[i])#(random(), random(), random())
        if isinstance(polygons, Polygon):
            polygons = [polygons]
        for poly in polygons:
            longitudes = [i[0] for i in poly.exterior.coords]
            latitudes = [i[1] for i in poly.exterior.coords]
            if not fill:
                ax.plot(longitudes, latitudes, transform=ccrs.PlateCarree(), color='k', zorder=0)
            else:
                ax.fill(longitudes, latitudes, transform=ccrs.PlateCarree(), color=c, zorder=0, alpha=0.5)
                # Remove interior
                if 0:
                    interior_polys = list(poly.interiors)
                    for i_poly in interior_polys:
                        longitudes = [i[0] for i in i_poly.coords]
                        latitudes = [i[1] for i in i_poly.coords]
                        ax.fill(longitudes, latitudes, transform=ccrs.PlateCarree(), color='white', zorder=0)

    if show:
        plt.show()
    else:
        return ax


"""Module for bot commands."""
import discord

class Commands:
    """class to supply command-functionality for the bot."""

    def __init__(self, translation, validation):
        self.translation = translation
        self.message_stats = validation

    def commands(self, command, enable_translate):
        """Method to process bot commands."""
        return {
            "help": self.get_help(),
            "start": self.trigger_translation(command, enable_translate),
            "stop": self.trigger_translation(command, enable_translate),
            "status": self.get_status(enable_translate),
            "stats": self.get_stats(
                language_stats=self.translation.get_language_counts(),
                message_stats=self.message_stats.get_message_count(),
            ),
        }[command]

    @staticmethod
    def get_help():
        """Method to create help response."""
        help_text = discord.Embed(
            title="**Info**",
            colour=discord.Colour(0xF8E71C),
            description="this bot automatically translates messages "
            "from english to german, french and russian and vice versa",
        )
        help_text.set_footer()
        help_text.add_field(
            name="**Commands**",
            value="```\n $start - starts translation "
            "\n $stop - stops translation "
            "\n $status - shows if translation is active or not```",
        )

        return {"embed": help_text}

    @staticmethod
    def trigger_translation(command: str, enable_translate: bool):
        """Method to trigger translation."""
        if command == "start" and not enable_translate:
            state = discord.Embed(
                colour=discord.Colour(0x7ED321), description="start translation.."
            )

        elif command == "stop" and enable_translate:
            state = discord.Embed(
                colour=discord.Colour(0xD0021B), description="translation stopped.."
            )
        else:
            state = ""

        return {"embed": state}

    @staticmethod
    def get_status(translation_state):
        """Method to apply status response."""
        if translation_state:
            cur_status = "translation running."
        else:
            cur_status = "translation not running."

        status_embed = discord.Embed(
            colour=discord.Colour(0xF5A623), description=f"{cur_status}"
        )

        return {"embed": status_embed}

    @staticmethod
    def get_stats(language_stats: dict, message_stats: dict):
        """Method to render embed for language statistics."""
        stats_embed = discord.Embed(
            colour=discord.Colour(0xF5A623),
            title="**Stats**",
            description="```"
            f"German: {language_stats['de']} Messages\n"
            f"English: {language_stats['en']} Messages\n"
            f"French: {language_stats['fr']} Messages\n"
            f"Other: {language_stats['other']} Messages\n"
            f"\n"
            f"Messages translated: {message_stats['translated']} \n"
            f"Messages not translated: {language_stats['other']} \n"
            f"\n"
            f"Emoji-Messages: {message_stats['emojis']} \n"
            f"Command-Messages: {message_stats['commands']} \n"
            f"Image-Messages: {message_stats['images']} \n"
            f"\n"
            f"Total user messages handled: {message_stats['all']} \n"
            f"Total bot messages: {message_stats['bot']} \n"
            "```",
        )

        return {"embed": stats_embed}


from flask import request
from flask import current_app
import json
import requests

def main():
    eventType = request.headers["X-Kubernetes-Event-Type"]
    objType = request.headers["X-Kubernetes-Object-Type"]

    if objType != "Pod":
        return ""

    pod = request.get_json()
    if (pod == None) or ("status" not in pod) or ("containerStatuses" not in pod["status"]):
        current_app.logger.info("No container status information in event")
        return ""

    podName = pod["metadata"]["name"]

    states = summarizeContainerStates(pod)

    # Notify clients
    notification = { "containerStates": states, "podName": podName }
    r = requests.post("http://podvis.default:8001/", data=json.dumps(notification))
    current_app.logger.info("Pod vis event post status: %s" % r.status_code)

    return ""

def summarizeContainerStates(pod):
    containerStatuses = pod["status"]["containerStatuses"]
    states = {}
    for cs in containerStatuses:
        name = cs["name"]
        state = cs["state"]
        if "running" in state:
            states[name] = "running"
        elif "terminated" in state:
            states[name] = "terminated"
        else:
            s = "waiting"
            if ("waiting" in state) and ("reason" in state["waiting"]):
                s = state["waiting"]["reason"]
            states[name] = s

    current_app.logger.info("C states = %s" % states)
    return states


import sqlite3
import pandas
import psycopg2
import psycopg2.extras
from db.config import config
from core.utils import CustomLogger

log = CustomLogger()

def connect_start():
    """ Connect to the PostgreSQL database server """
    log.info('connect_start :: Connecting to the PostgreSQL database')
    conn = None
    try:
        # read connection parameters
        params = config(filename='./db/database.ini', section="profile_saurav")
        conn = psycopg2.connect(**params)
        cur = conn.cursor()
        cur.execute('SELECT version()')
        db_version = cur.fetchone()
        print(db_version)
        if db_version:
            log.info('connect_start :: Connection Established - {}'.format(db_version))
            cur.close()
            return conn
        raise Exception
    except (Exception, psycopg2.DatabaseError) as error:
        log.error('connect_error :: Failed to Establish Connection :: {}'.format(error))

def connect_end(conn):
    """ Close Connection """
    try:
        conn.close()
        log.info('connect_end :: Connection Closed')
    except Exception as error:
        log.error('connect_end :: Failed to Close Connection :: {}'.format(error))

def is_existing_table(conn, table: str) -> bool:
    TABLE_EXISTS_QUERY = """
        SELECT EXISTS (
            SELECT FROM
                pg_tables
            WHERE
                tablename  = '{}'
        );
    """
    cur = conn.cursor()
    cur.execute(TABLE_EXISTS_QUERY.format(table))
    row = cur.fetchone()
    cur.close()
    if row[0]:
        log.info(f"is_existing_table :: {table} :: True")
        return True
    log.info(f"is_existing_table :: {table} :: False")
    return False

def create_table(conn, table, columns):
    cur = conn.cursor()
    CREATE_QUERY = """
        CREATE TABLE {table} ({columns});
    """
    for i in range(len(columns)):
        columns[i] = columns[i] + " varchar(255)"
    columns_ddl: str = ', '.join(columns)
    cur.execute(CREATE_QUERY.format(table = table, columns = columns_ddl))
    cur.close()
    log.info(f"create_table :: {table} created")

def prep(i):
    i = i.replace('\"','')
    return i.replace("\'",'\"')

def prep_dataframe(df: pandas.DataFrame, prep_columns: list):
    for i in df.columns:
        df[i] = df[i].astype(str)
    for col in prep_columns:
        df[col] = df[col].apply(prep)
    return df

def insert_into_table_from_dataframe(conn, df: pandas.DataFrame, table: str, prep_columns: list = []):
    log.start("insert_into_table_from_dataframe")
    cur = conn.cursor()
    df = prep_dataframe(df, prep_columns)
    INSERT_QUERY = """
        INSERT INTO {table} ({columns})
        VALUES {values};
    """
    columns = ', '.join(df.columns)
    values: list = []
    for row in df.values:
        values.append(str(tuple(row)))
    values = ','.join(values)
    log.info(f"insert_into_table_from_dataframe :: inserting data for {table}...")
    cur.execute(INSERT_QUERY.format(table = table, columns = columns, values = values))
    cur.close()
    log.end("insert_into_table_from_dataframe")

if __name__ == '__main__':
    pass


from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _

from rest_framework.serializers import RelatedField, SerializerMethodField

from django_userforeignkey.request import get_current_user

from eric.core.rest.serializers import BaseModelWithCreatedBySerializer
from eric.relations.models import Relation

class RelationObjectRelatedField(RelatedField):
    """
    Related Field which converts a given value via its default serializer
    """

    def to_representation(self, value):

        # if the object is none, we dont have permission to view it
        if value is None:
            data = {"error": True, "detail": _("No permission to view this element")}
            return data

        # get default serializer class
        serializer_cls = value._meta.get_default_serializer()
        # serialize the object
        serializer = serializer_cls(value, context=self.context)

        return serializer.data

class RelationSerializerExtended(BaseModelWithCreatedBySerializer):
    """Serializer for Relations"""

    left_content_object = RelationObjectRelatedField(read_only=True, many=False)
    right_content_object = RelationObjectRelatedField(read_only=True, many=False)

    left_content_type_model = SerializerMethodField()

    right_content_type_model = SerializerMethodField()

    def get_left_content_type_model(self, instance):
        if hasattr(instance, "left_content_type"):
            return "{app_label}.{model}".format(
                app_label=instance.left_content_type.app_label,
                model=instance.left_content_type.model,
            )

        return None

    def get_right_content_type_model(self, instance):
        if hasattr(instance, "right_content_type"):
            return "{app_label}.{model}".format(
                app_label=instance.right_content_type.app_label,
                model=instance.right_content_type.model,
            )

        return None

    class Meta:
        model = Relation
        fields = (
            "left_content_type",
            "left_object_id",
            "left_content_object",
            "right_content_type",
            "right_object_id",
            "right_content_object",
            "created_by",
            "private",
            "left_content_type_model",
            "right_content_type_model",
        )
        read_only_fields = ("left_content_object", "right_content_object")

    def update(self, instance, validated_data):
        """updates the private field only when the current user is the creator"""
        user = get_current_user()
        if instance.created_by.pk == user.pk and not user.is_anonymous:
            instance.private = validated_data["private"]
            instance.save()
        else:
            raise ValidationError(
                {
                    "private": ValidationError(
                        _("You are not allowed to change it."), params={"relation": self}, code="invalid"
                    )
                }
            )
        return instance


import rocketcea
import os
import numpy as np
import scipy
import matplotlib.pyplot as plt
from rocketcea.cea_obj import CEA_Obj
import matplotlib
import pandas as pd
import math

#data from new_thrust.py
rho_ox = 1141
rho_eth = 798
Cd_eth = [0.0000172046,0.000010777] #T-I, I-C
Cd_ox = [0.0000258069,0.0000113]
P_tank_ox = [346.93,
            346.93,
            346.93,
            346.93,
            346.93,
            346.93,
            335.3,
            321.62,
            308.77,
            296.94,
            289.71,
            282.74,
            276.07,
            269.65,
            263.48,
            254.71,
            243.83,
            236.36,
            229.26,
            222.58,
            216.2,
            207.92,
            201.01]
P_tank_eth = [422.23,
            422.06,
            397.65,
            376.08,
            355.84,
            337.49,
            321.29,
            307.5,
            295.36,
            287.68,
            280.47,
            274.38,
            268.39,
            262.33,
            257.1,
            249.09,
            239.81,
            233,
            227.43,
            221.49,
            216.04,
            211.31,
            205.93]
P_c_exp = [-2.34,
                -2.01,
                187.49,
                223.75,
                229.11,
                223.41,
                212.39,
                200.05,
                187.49,
                179.65,
                171.88,
                164.55,
                157.74,
                151.51,
                145.46,
                137.35,
                127.78,
                121.42,
                115.82,
                110.19,
                101.59,
                28.34,
                10.1]
P_c_exp = np.array(P_c_exp) + 4
P_tank_eth = np.array(P_tank_eth) - 27
P_tank_ox = np.array(P_tank_ox)

#lumped resistance terms
Cd_ox_tot = Cd_ox[0]*Cd_ox[1]/(Cd_ox[0] + Cd_ox[1])
Cd_eth_tot = Cd_eth[0]*Cd_eth[1]/(Cd_eth[0] + Cd_eth[1])

#reference thrust curve
time = np.linspace(0, 10, 20) #20 pts from 0 to 10 seconds
thrust = np.linspace(5000, 200, 20)

#terminates either within a specified range of the zero (tol) or after a specified number of iterations (n)
def bisection(f, a, b, tol, n = 100):

    header = "{:<10s}{:<15s}{:<15s}{:<15s}{:<10s}".format('j', 'a', 'b', 'p', 'f(p)')
    print(header)
    print('-' * (len(header)-6))

    for j in range(n):
        p = (a + b) / 2
        if p - a < tol:
            return p

        print(f"{j:2d}  {a:12.8f}  {b:12.8f}  {p:12.8f}  {f(p):12.8f}")

        if f(a) * f(p) > 0:
            a = p
        else:
            b = p
    print("\nafter", n, "iterations, estimate is", p)
    return p

def guesstimate(thrust , time): #takes an array of thrust vs time (say like 20 data points? idk. Uses the profile to "guess flowrate using CEA and Cds")
    t = len(time)
    chamber = CEA_Obj(propName="", oxName="LOX", fuelName="C2H5OH") #initializs CEA object
    OF = 0.9 #ideal

    #lambda funcs
    mdot_ox = lambda P_c, P_tank_ox: Cd_ox_tot * ((2*rho_ox*(abs(P_c - P_tank_ox)))**(1/2))
    mdot_eth = lambda P_c, P_tank_eth: Cd_eth_tot * ((2*rho_eth*(abs(P_c - P_tank_eth)))**(1/2))

    mdot_2 = lambda P_c, P_tank_ox, P_tank_eth: mdot_ox(P_c, P_tank_ox) + mdot_eth(P_c, P_tank_eth)
    # mdot_1 = lambda thrust, P_c_exp, OF, eps=4.35: thrust/(chamber.estimate_Ambient_Isp(P_c_exp,OF,eps=4.35)[0]*9.8) #pasted in nonlambda form to avoid confusion

    output = [] #estimated chamber pressures
    objective_vals = [] #for plotting

    for i in range(t): #perform this for every point on the profile
        #fix the thrust, tank pressures, chamber pressure vals for each iteration
        thrust_i = thrust[i]
        P_tank_eth_i = P_tank_eth[i]
        P_tank_ox_i = P_tank_ox[i]
        P_c_exp_i = P_c_exp[i]
        objective = lambda P_c : thrust_i / ((chamber.estimate_Ambient_Isp(P_c_exp_i, OF, eps = 4.35)[0]) * 9.8) - mdot_2(P_c, P_tank_ox_i, P_tank_eth_i)
        root = bisection(objective, 0, 500, 1e-10, 1000)
        output.append(root)

        #for plotting
        obj_val = objective(root)
        objective_vals.append(obj_val)

    objective_vals = np.array(objective_vals)
    plt.figure()
    plt.plot(time, objective_vals)
    plt.xlabel('Time')
    plt.ylabel('Objective Function Value at an estimated root why is this so sus')
    plt.title('Objective Function Value vs Time')
    plt.grid(True)
    plt.show()

    print(output)
    return output

guesstimate(thrust , time)
#-------------------------

# def guesstimate(thrust , time): #takes an array of thrust vs time (say like 20 data points? idk. Uses the profile to "guess flowrate using CEA and Cds")
#     t = len(time)
#     chamber = CEA_Obj(propName="", oxName="LOX", fuelName="C2H5OH") #initializs CEA object
#     OF = 0.9 #ideal
#     output = []
#     '''change the lambda funcs below'''
#     effective_res_ox = lambda P_c,P_ox : 0*P_c   #type in derived expression for calculating flowrate from an effective resistance and given chamber pressure
#     effective_res_eth = lambda P_c,P_eth : 0*P_c
#     for i in range(t):
#         #perform this for every point on the profile
#         objective = lambda P_c : chamber.estimate_Ambient_Isp(P_c,OF,eps=4.35)[0] - (effective_res_ox(P_c,pressure_ox) + effective_res_eth(P_c,pressure_eth)) #fixed pt mdot - mdot!
#         #ADD BISECTION METHOD CODE HERE
#         bisection(objective, )

#         output.append()

#     return output

LE2 Simulation and Analysis/flow_guestimator_1.py


from django.db import models
from django.urls import reverse
from django.template.defaultfilters import slugify

class ProductCategory(models.Model):
    category_name = models.CharField(max_length=30)
    is_navbar = models.BooleanField(default=True)
    parent_category = models.ForeignKey("ProductCategory", on_delete=models.CASCADE, related_name="category_parent", null=True, blank=True)

    def __str__(self):
        return self.category_name

    def save(self, *args, **kwargs):
        if self.parent_category == None:
            self.is_navbar = True
        else:
            self.is_navbar = False
        return super().save()

    class Meta:
        verbose_name = "Category"
        verbose_name_plural = "Categories"

class Size(models.Model):
    name = models.CharField(max_length = 30)

    def __str__(self):
        return f"{self.name} size"

class Color(models.Model):
    name = models.CharField(max_length = 30)

    def __str__(self):
        return self.name

class Products(models.Model):
    discounts = (
        (5, '5'),
        (10, '10'),
        (15, '15'),
        (20, '20'),
        (25, '25'),
        (30, '30'),
        (35, '35'),
        (40, '40'),
        (45, '45'),
        (50, '50')
    )
    cover_image = models.ImageField(upload_to="product_images")
    title = models.CharField(max_length = 50)
    slug = models.SlugField(null=True, blank=True)
    short_descriptions = models.TextField()
    long_descriptions = models.TextField()
    in_sale = models.BooleanField(default=False)
    price = models.FloatField()
    discount = models.IntegerField(choices=discounts, null=True, blank=True)
    new_price = models.FloatField(null=True, blank=True)
    category_id = models.ForeignKey(ProductCategory, on_delete=models.CASCADE, related_name='product_category')
    created_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return self.title

    def get_absolute_url(self):
        return reverse("product_detail", kwargs={"slug": self.slug})

    def save(self, *args, **kwargs):
        self.slug = slugify(f'{self.title}')
        if self.in_sale:
            self.new_price = self.price - self.price*(self.discount/100)
        return super().save(*args, **kwargs)

    class Meta:
        verbose_name = "Product"
        verbose_name_plural = "Products"

class ProductVersion(models.Model):
    in_stock = models.PositiveIntegerField(default=0)
    rate_avg = models.FloatField(default=0)
    review_count = models.PositiveIntegerField(default=0)
    read_count = models.PositiveIntegerField(default=0)
    size_id = models.ManyToManyField(Size, blank=True, related_name="product_size")
    color_id = models.ManyToManyField(Color, blank=True, related_name="product_color")
    product_id = models.ForeignKey(Products, on_delete=models.CASCADE, related_name="product_version")
    created_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now=True)

    def __str__(self):
        return f"{self.product_id.title}'s version"

    class Meta:
        verbose_name = "Product Version"
        verbose_name_plural = "Product Versions"

class Image(models.Model):
    image = models.ImageField(upload_to="product_images")
    product_version_id = models.ForeignKey(ProductVersion, on_delete=models.CASCADE, related_name='product_version_image')

    def __str__(self):
        return f"{self.product_version_id.product_id.title}'s photo"

    class Meta:
        verbose_name = "Product Image"
        verbose_name_plural = "Product Images"

class ProductReview(models.Model):
    Rates = (
        (1, "20"),
        (2, "40"),
        (3, "60"),
        (4, "80"),
        (5, "100")
    )
    product_rate = models.IntegerField(choices=Rates)
    name = models.CharField(max_length=30)
    surname = models.CharField(max_length=30)
    review = models.TextField()
    created_at = models.DateTimeField(auto_now_add=True)
    update_at = models.DateTimeField(auto_now=True)
    product_version_id = models.ForeignKey(ProductVersion, on_delete = models.CASCADE, related_name="product_review")

    def __str__(self):
        return f"{self.name}'s reviews"

    class Meta:
        verbose_name = "Product Review"
        verbose_name_plural = "Product Reviews"


import math
import matplotlib.pyplot as plt
from .Generaldistribution import Distribution

class Uniform(Distribution):
    """ Uniform distribution class for calculating and
    visualizing a Uniform distribution.

    Attributes:
        mean (float) representing the mean value of the distribution
        stdev (float) representing the standard deviation of the distribution
        data_list (list of floats) extracted from the data file
        low (float) representing the smallest number in data_list
        high (float) representing the highest number in data_list
    """

    def __init__(self, low=0, high=10):
        if low == high:
            raise Exception('Invalid interval -  start and end of interval cannot be the same')
        self.low = low
        self.high = high
        Distribution.__init__(self, self.calculate_mean(),
                              self.calculate_stdev())

    def replace_stats_with_data(self):
        """Function to calculate low and high from the data set

                # Args:
                    None

                Returns:
                    float: the low value
                    float: the high value
                """
        self.low = min(self.data)
        self.high = max(self.data)
        if self.low == self.high:
            raise Exception('Invalid interval -  start and end of interval cannot be the same')
        self.calculate_mean()
        self.calculate_stdev()
        return self.low, self.high

    def calculate_mean(self, round_to=2):
        """Function to calculate the mean of the data set.

        Args:
             round_to (int): Round the mean value. [Default value: 2 floating point]

        Returns:
               float: mean of the data set
        """

        self.mean = (self.low + self.high) / 2

        return round(self.mean, round_to)

    def calculate_stdev(self, sample=True, round_to=2):
        """Function to calculate the standard deviation of the data set.

        Args:
             sample (bool): whether the data represents a sample or population
             round_to (int): Round the mean value. [Default value: 2 floating point]

        Returns:
            float: standard deviation of the data set
        """

        sqr_interval = (self.high - self.low) ** 2

        self.stdev = math.sqrt(sqr_interval / 12)

        return round(self.stdev, round_to)

    def calculate_cdf(self, x, round_to=2):
        """Cumulative distribution function calculator for the uniform distribution.

                Args:
                        x (float): point for calculating the
                                   cumulative distribution function

                Returns:
                        float: cumulative distribution function output
                """
        if x < self.low:
            self.cdf = 0
        elif self.low<=x<=self.high:
            self.cdf = (x - self.low)/(self.high-self.low)
        else:
            self.cdf = 1

        return round(self.cdf, round_to)

    def plot_histogram(self):
        """Function to output a histogram of the instance variable data using
                matplotlib pyplot library.

                Args:
                        None

                Returns:
                        None
                """
        plt.hist(self.data)
        plt.title("Histogram of Data")
        plt.xlabel("data")
        plt.ylabel("count")
        plt.show()

    def calculate_pdf(self, x, round_to=2):
        """Probability density function calculator for the uniform distribution.

                Args:
                        x (float): point for calculating the
                                   probability density function
                        round_to (int): Round the mean value. [Default value: 2 floating point]

                Returns:
                        float: probability density function output
        """
        self.pdf = 1/(self.high-self.low) if self.high >= x >= self.low else 0
        return round(self.pdf, round_to)

    def plot_bar_pdf(self):
        """Function to plot the pdf of the uniform distribution

        Args:
            None

        Returns:
            list: x values for the pdf plot
            list: y values for the pdf plot
        """

        x = []
        y = []

        # calculate the x values to visualize
        for i in range(int(self.low)-5, int(self.high)+5):
            x.append(i)
            self.calculate_pdf(i)
            y.append(self.pdf)

        # make the plots
        plt.bar(x, y)
        plt.title('Probability Density for Uniform Distribution')
        plt.ylabel('Probability')
        plt.xlabel('x')

        plt.show()

        return x, y

    def __repr__(self):
        """Function to output the characteristics of the Uniform instance

                Args:
                        None

                Returns:
                        string: characteristics of the Uniform distribution

                """

        return "mean {}, standard deviation {}".format(self.mean, self.stdev)


import pytest
from hypothesis import given, assume
from hypothesis.strategies import integers, booleans

from gen_factor_sat import utils
from gen_factor_sat.circuit.instances import TseitinFactoringStrategy

@pytest.fixture(scope='module')
def tseitin_circuit():
    return TseitinFactoringStrategy()

@given(number_1=integers(min_value=0), number_2=integers(min_value=0), carry=booleans())
def test_n_bit_adder(tseitin_circuit, number_1, number_2, carry):
    bin_number_1 = utils.to_bin_list(number_1)
    bin_number_2 = utils.to_bin_list(number_2)
    bin_carry = utils.to_bin_list(carry)[0]

    bin_result = tseitin_circuit.n_bit_adder(bin_number_1, bin_number_2, bin_carry, None)

    assert len(bin_result) == max(len(bin_number_1), len(bin_number_2)) + 1
    assert utils.to_int(bin_result) == number_1 + number_2 + int(carry)

@given(number_1=integers(min_value=0), number_2=integers(min_value=0))
def test_subtract(tseitin_circuit, number_1, number_2):
    assume(number_1 >= number_2)
    bin_number_1 = utils.to_bin_list(number_1)
    bin_number_2 = utils.to_bin_list(number_2)

    bin_result = tseitin_circuit.subtract(bin_number_1, bin_number_2, None)

    assert len(bin_result) == max(len(bin_number_1), len(bin_number_2))
    assert utils.to_int(bin_result) == number_1 - number_2

@given(number_1=integers(min_value=0), number_2=integers(min_value=0))
def test_n_bit_equality(tseitin_circuit, number_1, number_2):
    bin_number_1 = utils.to_bin_list(number_1)
    bin_number_2 = utils.to_bin_list(number_2)

    bin_result = tseitin_circuit.n_bit_equality(bin_number_1, bin_number_2, None)

    assert (bin_result == '1') == (number_1 == number_2)

@given(number_1=integers(min_value=0), number_2=integers(min_value=0))
def test_align(tseitin_circuit, number_1, number_2):
    bin_number_1 = utils.to_bin_list(number_1)
    bin_number_2 = utils.to_bin_list(number_2)

    aligned_xs, aligned_ys = tseitin_circuit.align(bin_number_1, bin_number_2, None)

    assert len(aligned_xs) == max(len(bin_number_1), len(bin_number_2))
    assert len(aligned_ys) == max(len(bin_number_1), len(bin_number_2))

    assert utils.to_int(aligned_xs) == number_1
    assert utils.to_int(aligned_ys) == number_2

@given(number=integers(min_value=0), shifts=integers(min_value=0, max_value=10))
def test_shift(tseitin_circuit, number, shifts):
    bin_number = utils.to_bin_list(number)

    shifted_xs = tseitin_circuit.shift(bin_number, shifts, None)

    assert len(shifted_xs) == len(bin_number) + shifts
    assert utils.to_int(shifted_xs) == (2 ** shifts) * number


from functools import reduce
from itertools import groupby

from pypokerengine.engine.hand_evaluator import HandEvaluator
from pypokerengine.engine.pay_info import PayInfo

class GameEvaluator:

  @classmethod
  def judge(self, table):
    winners = self.__find_winners_from(table.get_community_card(), table.seats.players)
    hand_info = self.__gen_hand_info_if_needed(table.seats.players, table.get_community_card())
    prize_map = self.__calc_prize_distribution(table.get_community_card(), table.seats.players)
    return winners, hand_info, prize_map

  @classmethod
  def create_pot(self, players):
    side_pots = self.__get_side_pots(players)
    main_pot = self.__get_main_pot(players, side_pots)
    return side_pots + [main_pot]

  @classmethod
  def __calc_prize_distribution(self, community_card, players):
    prize_map = self.__create_prize_map(len(players))
    pots = self.create_pot(players)
    for pot in pots:
      winners = self.__find_winners_from(community_card, pot["eligibles"])
      prize = int(pot["amount"] / len(winners))
      for winner in winners:
        prize_map[players.index(winner)] += prize
    return prize_map

  @classmethod
  def __create_prize_map(self, player_num):
    def update(d, other): d.update(other); return d
    return reduce(update, [{i:0} for i in range(player_num)], {})

  @classmethod
  def __find_winners_from(self, community_card, players):
    score_player = lambda player: HandEvaluator.eval_hand(player.hole_card, community_card)

    active_players = [player for player in players if player.is_active()]
    scores = [score_player(player) for player in active_players]
    best_score = max(scores)
    score_with_players = [(score, player) for score, player in zip(scores, active_players)]
    winners = [s_p[1] for s_p in score_with_players if s_p[0] == best_score]
    return winners

  @classmethod
  def __gen_hand_info_if_needed(self, players, community):
    active_players = [player for player in players if player.is_active()]
    gen_hand_info = lambda player: { "uuid": player.uuid, "hand" : HandEvaluator.gen_hand_rank_info(player.hole_card, community) }
    return [] if len(active_players) == 1 else [gen_hand_info(player) for player in active_players]

  @classmethod
  def __get_main_pot(self, players, sidepots):
    max_pay = max([pay.amount for pay in self.__get_payinfo(players)])
    return {
        "amount": self.__get_players_pay_sum(players) - self.__get_sidepots_sum(sidepots),
        "eligibles": [player for player in players if player.pay_info.amount == max_pay]
    }

  @classmethod
  def __get_players_pay_sum(self, players):
    return sum([pay.amount for pay in self.__get_payinfo(players)])

  @classmethod
  def __get_side_pots(self, players):
    pay_amounts = [payinfo.amount for payinfo in self.__fetch_allin_payinfo(players)]
    gen_sidepots = lambda sidepots, allin_amount: sidepots + [self.__create_sidepot(players, sidepots, allin_amount)]
    return reduce(gen_sidepots, pay_amounts, [])

  @classmethod
  def __create_sidepot(self, players, smaller_side_pots, allin_amount):
    return {
        "amount": self.__calc_sidepot_size(players, smaller_side_pots, allin_amount),
        "eligibles" : self.__select_eligibles(players, allin_amount)
    }

  @classmethod
  def __calc_sidepot_size(self, players, smaller_side_pots, allin_amount):
    add_chip_for_pot = lambda pot, player: pot + min(allin_amount, player.pay_info.amount)
    target_pot_size = reduce(add_chip_for_pot, players, 0)
    return target_pot_size - self.__get_sidepots_sum(smaller_side_pots)

  @classmethod
  def __get_sidepots_sum(self, sidepots):
    return reduce(lambda sum_, sidepot: sum_ + sidepot["amount"], sidepots, 0)

  @classmethod
  def __select_eligibles(self, players, allin_amount):
    return [player for player in players if self.__is_eligible(player, allin_amount)]

  @classmethod
  def __is_eligible(self, player, allin_amount):
    return player.pay_info.amount >= allin_amount and \
        player.pay_info.status != PayInfo.FOLDED

  @classmethod
  def __fetch_allin_payinfo(self, players):
    payinfo = self.__get_payinfo(players)
    allin_info = [info for info in payinfo if info.status == PayInfo.ALLIN]
    return sorted(allin_info, key=lambda info: info.amount)

  @classmethod
  def __get_payinfo(self, players):
    return [player.pay_info for player in players]


import numpy as np
import scipy.io as scio
from tqdm import tqdm
from .image_dataset import ImageDataset

class HierarchicalSynthDataset(ImageDataset):
    r'''Dataset reading from images.
    Args:
        Processes: A series of Callable object, which accept as parameter and return the data dict,
            typically inherrited the `DataProcess`(data/processes/data_process.py) class.
    '''

    def __init__(self, data_dir=None, data_list=None, cmd={}, **kwargs):
        self.load_all(**kwargs)
        self.data_dir = data_dir or self.data_dir
        self.data_list = data_list or self.data_list

        self.is_training = True

        self.debug = cmd.get('debug', False)

        self.image_paths = []
        self.targets = []

        self.parts = kwargs.get('parts', 3)

        print("Initializing SynthDataset")
        self.get_all_samples()

    def get_all_samples(self):
        for i in range(len(self.data_dir)):
            data = scio.loadmat(self.data_list[i])

            img_paths = list(data['imnames'][0])
            gts = list(data['wordBB'][0])
            texts = list(data['txt'][0])

            img_paths=[self.data_dir[i]+'/img/'+timg[0].strip() for timg in img_paths]
            self.image_paths += img_paths

            for gt, text in tqdm(zip(gts,texts)):
                bboxes, words = self.load_ann(gt, text)

                lines = []
                for k in range(bboxes.shape[0]):
                    polys = self._decode_polys(bboxes[k])
                    item = {
                        'seg_polys':polys,
                        'poly':bboxes[k].tolist(),
                        'text':words[k]
                    }
                    lines.append(item)
                self.targets.append(lines)
        self.num_samples = len(self.image_paths)

    def load_ann(self, gt, text):
        bboxes = np.array(gt)
        bboxes = np.reshape(bboxes, (bboxes.shape[0], bboxes.shape[1], -1))
        bboxes = bboxes.transpose(2, 1, 0)
        bboxes = np.round(bboxes)

        words = []
        for text_ in text:
            text_ = text_.replace('\n', ' ').replace('\r', ' ')
            words.extend([w for w in text_.split(' ') if len(w) > 0])

        return bboxes, words

    def _decode_polys(self, bbox):
        step = 1
#         bbox = np.array(bbox).reshape(-1)
#         assert bbox.shape[0]==8
        bbox = np.array(bbox).reshape(4,2)
        bbox = self.check_clockwise(bbox)
        bbox = self.check_bbox(bbox)
        bbox = np.array(bbox).reshape(-1)
        new_x_t = np.linspace(float(bbox[0]), float(bbox[2]), num=step*self.parts+1)
        new_y_t = np.linspace(float(bbox[1]), float(bbox[3]), num=step*self.parts+1)
        new_x_b = np.linspace(float(bbox[4]), float(bbox[6]), num=step*self.parts+1)
        new_y_b = np.linspace(float(bbox[5]), float(bbox[7]), num=step*self.parts+1)
        new_x = np.concatenate((new_x_t, new_x_b), axis=0)
        new_y = np.concatenate((new_y_t, new_y_b), axis=0)
        points = np.stack((new_x, new_y), axis=-1)

        polys = []
        u_ind = 0
        b_ind = points.shape[0]
        for i in range(self.parts):
            poly = np.concatenate((points[u_ind:u_ind+step+1],points[b_ind-step-1:b_ind]), axis=0)
            polys.append(poly.reshape((-1, 2)).tolist())
            u_ind = u_ind+step
            b_ind = b_ind-step
        return polys

    def check_bbox(self, bbox):
        xmin, xmax = min(bbox[:,0]), max(bbox[:,0])
        w = xmax-xmin
        ymin, ymax = min(bbox[:,1]), max(bbox[:,1])
        h = ymax-ymin
        d = np.sum((bbox-np.array([[xmin,ymin]]))**2,axis=1)
        start_ind = np.argmin(d)
        bbox = np.roll(bbox, -start_ind, axis=0)
        return bbox

    def check_clockwise(self, points):
        y = points[:, 1]
        idx = np.argmax(y)
        x1 = points[(idx - 1 + len(points)) % len(points)]
        x2 = points[idx]
        x3 = points[(idx + 1) % len(points)]
        x2_x1 = x2 - x1
        x3_x2 = x3 - x2
        judge_result = x2_x1[0] * x3_x2[1] - x2_x1[1] * x3_x2[0]
        if judge_result < 0:
            points = points[::-1]

        return points


from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import csv
import re
import time

# turn on selenium
driver = webdriver.Chrome(r'\Users\aviia\desktop\chromedriver.exe')

# go to the ranking area of the site
driver.get("https://boardgamegeek.com/browse/boardgame")

csv_file = open('bdgeekallratings.csv', 'w', encoding='utf-8', newline='')
writer = csv.writer(csv_file)

# it takes 56 seconds to do 2 pages
# it takes 107 seconds to do 4 pages
# 185 pages should take about 1 hours 23 minutes if everthing is somewhat consistent.
# index*100 is the amount of games rank you are going to find

index = 1
while index < 186:
    try:
        print('\n')
        print("Scraping Page number " + str(index))
        print("+"*40)
        index += 1

        wait_rating = WebDriverWait(driver, 10)
        ratings = wait_rating.until(EC.presence_of_all_elements_located((By.XPATH,'//div/table[@class="collection_table"]//tr')))

        for rating in ratings:
            game_rating_dict = {}
            time.sleep(0)

            # print out the rank of the game
            try:
                rank = rating.find_element_by_xpath('.//*[contains(@class, "collection_rank")]').text
                print(rank)
            except:
                continue

            # # print out the title of the game
            try:
                title = rating.find_element_by_xpath('.//div[contains(@id, "results_objectname")]//a').text
                print(title)
            except:
                continue

            # # print out the geek rating of the game
            try:
                geek_rating = rating.find_element_by_xpath('.//td[4][contains(@class, "collection_bggrating")]').text
                print(geek_rating)
            except:
                continue

            # # print out the average rating of the game
            try:
                avg_rating = rating.find_element_by_xpath('.//td[5][contains(@class, "collection_bggrating")]').text
                print(avg_rating)
            except:
                continue

            # # print out the number of ratings of the game
            try:
                num_of_rating = rating.find_element_by_xpath('.//td[6][contains(@class, "collection_bggrating")]').text
                print(num_of_rating)
            except:
                continue

            game_rating_dict['rank'] = rank
            game_rating_dict['title'] = title
            game_rating_dict['geek_rating'] = geek_rating
            game_rating_dict['avg_rating'] = avg_rating
            game_rating_dict['num_of_rating'] = num_of_rating
            writer.writerow(game_rating_dict.values())

        first_next_page = driver.find_element_by_xpath('/html/body/div[2]/main/div/div[1]/div[1]/div/div/p/a[5]')
        first_next_page.click()

    except Exception as e:
        print(e)
        csv_file.close()
        driver.close()
        break


import torch as th
import numpy as np

def to_tensor(arr, gpu=False):
    tensor = th.tensor(arr).float()
    if gpu:
        return tensor.cuda()
    return tensor

def sample_triplets(anchor_idxs, x_to_class_dict, class_to_y_dict, num_triplets, sampling_method='random'):
    assert sampling_method in {'random'}
    if sampling_method == 'random':
        return sample_random_triplets(anchor_idxs, x_to_class_dict, class_to_y_dict, num_triplets)

def sample_random_triplets(anchor_idxs, x_to_class_dict, class_to_y_dict, num_triplets):
    classes = set(class_to_y_dict.keys())
    from tqdm import tqdm
    pos_idxs = []
    neg_idxs = []
    for anchor in tqdm(anchor_idxs):
        pos_class = x_to_class_dict[anchor]
        pos_idxs.append(sample_n(class_to_y_dict, [pos_class], num_triplets))
        neg_classes = list(classes - set([pos_class]))
        neg_idxs.append(sample_n(class_to_y_dict, neg_classes, num_triplets))
    pos_idxs = np.array(pos_idxs)
    neg_idxs = np.array(neg_idxs)
    return pos_idxs, neg_idxs

def sample_n(class_to_y_dict, classes, num_triplets):
    if len(classes) == 1:
        sample_classes = classes * num_triplets
    else:
        classes = np.array(classes)
        sample_classes = classes[np.random.randint(len(classes), size=num_triplets)]
    sampled_triplets = np.zeros(num_triplets, dtype=np.int)
    for i, class_ in enumerate(sample_classes):
        class_idxs = class_to_y_dict[class_]
        sampled_triplets[i] = class_idxs[np.random.randint(len(class_idxs))]
    return sampled_triplets

def convert_rel_dicts_to_uids(x_to_class, class_to_x, uid_to_idx):
    new_x_to_class = {}
    for uid in x_to_class:
        new_x = uid_to_idx[uid]
        new_x_to_class[new_x] = x_to_class[uid]
    new_class_to_x = {}
    for class_ in class_to_x:
        new_class_to_x[class_] = []
        for uid in class_to_x[class_]:
            new_class_to_x[class_].append(uid_to_idx[uid])
    return new_x_to_class, new_class_to_x


import regex as re
import json
from pprint import pprint

f = open("dweets.json", "r")
a = f.read()

a = re.sub("}]}{", "}]}, {")

data = json.load(a)

pprint(data[0])

best_dweets = []
for chunk in data:
      for dweet in chunk['results']:
          if dweet['awesome_count'] > 9:
              best_dweets.append(dweet)
pprint(best_dweets[0])


import cv2
import numpy as np
import sys
import math
import matplotlib.pyplot as plt
import open3d as o3d

from laser_scanner import LaserScanner

CAM_ID = 0
CAM_WIDTH = 640
CAM_HEIGHT = 480
THRESHOLD_MIN = 245
THRESHOLD_MAX = 255

CAM_FOV = 64.8 # deg
D_CL = 0.31 # distance between camera and laser position, in m
ALPHA_LASER = 28.67 # laser tilted by this much, degrees

#cap = cv2.VideoCapture(CAM_ID)

frame = cv2.imread("test_depth05.jpg")

laser_scanner = LaserScanner(math.radians(CAM_FOV), D_CL, math.radians(ALPHA_LASER))

laser_line, lline_data = laser_scanner.get_laser_line(frame, THRESHOLD_MIN, THRESHOLD_MAX)

coords = []

for i in range(len(lline_data)):

    x, y, z = laser_scanner.get_xyz(lline_data[i])

    #print(lline_data[i])
    print(y)

    if y > 0:
        coords += [[x, y, -z]]

coords = np.array(coords)
surf = coords.copy()
for i in range(20):
    shifted = coords.copy()
    #print(shifted)
    shifted[:, 2] += i / 800.0
    surf = np.append(surf, shifted, axis=0)

#np.set_printoptions(threshold=sys.maxsize)
#print(surf.shape)
#fig, ax = plt.subplots(subplot_kw={"projection": "3d"})
#ax.scatter(surf[:, 0], surf[:, 1], surf[:, 2])
#plt.show()

pc = o3d.geometry.PointCloud()
pc.points = o3d.utility.Vector3dVector(surf)
pc.estimate_normals(search_param=o3d.geometry.KDTreeSearchParamHybrid(radius=0.1, max_nn=30))
#print(np.asarray(pc.normals))

#radii = [0.005, 0.01]
#rec_mesh = o3d.geometry.TriangleMesh.create_from_point_cloud_ball_pivoting(pc, o3d.utility.DoubleVector(radii))
o3d.visualization.draw_geometries([pc], point_show_normal=True)

window_name = "Scan"
window = cv2.namedWindow(window_name, cv2.WINDOW_AUTOSIZE)

while True:

    #ret, frame = laser_scanner.get_camera_frame()
    #laser_line, lline_data = laser_scanner.get_laser_line(frame, THRESHOLD_MIN, THRESHOLD_MAX)

    # Display the resulting frame
    cv2.imshow('Scan', laser_line)

    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
    if cv2.getWindowProperty(window_name, cv2.WND_PROP_VISIBLE) < 1:
        break

# After the loop release the cap object
if laser_scanner.cap is not None:
    laser_scanner.cap.release()
# Destroy all the windows
cv2.destroyAllWindows()


import cv2
import gym
import gym.spaces
import numpy as np
import collections
import pdb

class ScaledFloatFrame(gym.ObservationWrapper):
    def observation(self, obs):

        res = np.array(obs).astype(np.float32) / 255.0
        #pdb.set_trace()
        return res

class MultiLives(gym.ObservationWrapper):
    def __init__(self, env):
        super(MultiLives, self).__init__(env)
        self.env = env
        self.lives = 0
        self.real_over = True
    def reset(self):
        if (self.real_over):
            state = self.env.reset()
            self.real_over = False
        else:
            state, _, _, _ = self.env.step(0)
        self.lives = self.env.unwrapped.ale.lives()
        return state
    def step(self, action):
        obs, reward, done, info = self.env.step(action)
        self.real_over = done
        # check current lives, make loss of life terminal,
        # then update lives to handle bonus lives
        lives = self.env.unwrapped.ale.lives()

        if lives < self.lives and lives > 0:
            # for Qbert sometimes we stay in lives == 0 condition for a few frames
            # so it's important to keep lives > 0, so that we only reset once
            # the environment advertises done.
            done = True
        self.lives = lives
        return obs, reward, done, info

class SkipFrame(gym.ObservationWrapper):
    def __init__(self, env, skip_number = 4):
        super(SkipFrame, self).__init__(env)
        self.SKIP = skip_number
    def reset(self):
        state = self.env.reset()
        self.COUNTER = self.SKIP
        return state
    def step(self, action):
        total_reward = 0
        states = []
        for i in range(self.SKIP):
            state, reward, terminal, _ = self.env.step(action)
            states.append(state)
            total_reward += reward
            if terminal:
                break
        return np.max(np.stack(states), axis = 0), total_reward, terminal, _

class ResizeGreyPic(gym.ObservationWrapper):
    def __init__(self, env, cut = [-1, -1], size = (84, 84)):
        super(ResizeGreyPic, self).__init__(env)
        self.SIZE = size
        self.observation_space = gym.spaces.Box(low = 0, high = 255, shape = (size[1], size[0], 1), dtype=np.float)
        self.cut = cut

    def observation(self, state):
        #print(state.shape)
        #pdb.set_trace()
        if len(state.shape) == 3:
            state = state.reshape(1, *state.shape)
        new_s_all = []
        for s in state:
            new_s = s[:,:,0] * 0.299 + s[:,:,1] * 0.587 + s[:,:,2] * 0.114
            if self.cut[0] != -1 or self.cut[1] != -1:
                new_s = new_s[self.cut[0]:self.cut[1], :]
                #new_s = new_s[34:194, :]#Pong
                #new_s = new_s[32:192, :]#Breakout
            new_s_all.append(new_s)
        #pdb.set_trace()
        new_s_all = np.stack(new_s_all).max(0)
        #print(new_s_all.shape)
        state = cv2.resize(new_s_all, self.SIZE, interpolation = cv2.INTER_AREA)
        state = state.reshape((*self.SIZE, 1)).astype(np.uint8)
        #plt.matshow(state)
        #delta = x_t - state
        #pdb.set_trace()
        #plt.show()
        return state# / 255.0

class ChangeAxis(gym.ObservationWrapper):
    def __init__(self, env):
        super(ChangeAxis, self).__init__(env)
        size = self.env.observation_space.shape
        self.observation_space = gym.spaces.Box(low = self.env.observation_space.low.reshape(-1)[0], high = self.env.observation_space.high.reshape(-1)[0], shape = (size[2], size[0], size[1]), dtype=self.env.observation_space.dtype)
    def observation(self, state):
        return state.transpose(2, 0, 1)

class CollectFrame(gym.ObservationWrapper):
    def __init__(self, env, collect_number = 4):
        super(CollectFrame, self).__init__(env)
        self.COLLECT = collect_number
        #pdb.set_trace()
        oldshape = self.env.observation_space.shape
        if len(oldshape) == 3:
            if oldshape[0] == 1: oldshape = (oldshape[1], oldshape[2])
            elif oldshape[2] == 1: oldshape = (oldshape[0], oldshape[1])
        #print(oldshape)
        self.observation_space = gym.spaces.Box(low=0, high=255, shape=(collect_number, *oldshape),
                                                dtype=self.env.observation_space.dtype)
        #pdb.set_trace()
    def reset(self):
        oldshape = self.env.observation_space.shape
        if len(oldshape) == 3:
            if oldshape[0] == 1: oldshape = (oldshape[1], oldshape[2])
            elif oldshape[2] == 1: oldshape = (oldshape[0], oldshape[1])
        self.states = np.zeros((self.COLLECT, *oldshape), dtype='float32')
        return self.observation(self.env.reset())
    def observation(self, state):
        if len(state.shape) == 3 and state.shape[0] == 1: state = state.reshape(state.shape[1], state.shape[2])
        if len(state.shape) == 3 and state.shape[2] == 1: state = state.reshape(state.shape[0], state.shape[1])
        self.states[:-1] = self.states[1:]
        self.states[-1] = state
        #pdb.set_trace()
        return self.states

def make_env(env_name):
    '''
    env = gym.make("PongNoFrameskip-v4")
    env = SkipFrame(env)
    env = ResizeGreyPic(env)
    #env = ChangeAxis(env)
    env = CollectFrame(env)
    return env
    '''
    cut = [-1, -1]
    if env_name[:4] == 'Pong':
        cut = [34, 194]
    elif env_name[:8] == 'Breakout':
        cut = [32, 192]
    env = gym.make(env_name)
    env = MultiLives(env)
    env = SkipFrame(env)
    env = ResizeGreyPic(env, cut)
    env = CollectFrame(env)
    env = ScaledFloatFrame(env)
    return env