"""
This module implements the ProjPicker GUI using tkinter.
"""

import tkinter as tk
from tkinter import ttk
from tkinter import filedialog
from tkinter import messagebox
import textwrap
import webbrowser
import threading
import queue
import functools

try:
    import pyproj
except:
    pyproj = None

if __package__:
    from . import projpicker as ppik
    from .gui_common import (get_latlon, get_zoom, get_dzoom, parse_geoms,
                             adjust_lon, calc_geoms_bbox, create_crs_info,
                             find_bbox)
    from .getosm import OpenStreetMap
else:
    import projpicker as ppik
    from gui_common import (get_latlon, get_zoom, get_dzoom, parse_geoms,
                            adjust_lon, calc_geoms_bbox, create_crs_info,
                            find_bbox)
    from getosm import OpenStreetMap

class AutoScrollbar(ttk.Scrollbar):
    """
    Implement the auto-hiding scrollbar widget.
    https://stackoverflow.com/a/66338658/16079666
    """
    def __init__(self, master, **kwargs):
        """
        Construct an auto scrollbar.

        Args:
            master (widget): Parent widget.
            **kwargs (keyword arguments): Keyword arguments for the auto
                scrollbar widget.
        """
        super().__init__(master, **kwargs)
        self.geometry_manager_add = lambda: None
        self.geometry_manager_forget = lambda: None

    def set(self, first, last):
        """
        Override the set() method.

        Args:
            first (float): First slider position between 0 and 1.
            last (float): Last slider position between 0 and 1.
        """
        if float(first) <= 0.0 and float(last) >= 1.0:
            self.geometry_manager_forget()
        else:
            self.geometry_manager_add()
        super().set(first, last)

    def grid(self, **kwargs):
        """
        Override the grid() method.

        Args:
            **kwargs (keyword arguments): Keyword arguments for grid().
        """
        self.geometry_manager_add = functools.partial(super().grid, **kwargs)
        self.geometry_manager_forget = super().grid_forget

    def pack(self, **kwargs):
        """
        Override the pack() method.

        Args:
            **kwargs (keyword arguments): Keyword arguments for pack().
        """
        # this method is called only once; let's remember its packing order now
        # https://stackoverflow.com/a/58456449/16079666
        siblings = self.master.pack_slaves()
        n = len(siblings)
        after = siblings[n - 1] if n > 0 else None
        self.geometry_manager_add = functools.partial(super().pack,
                                                      after=after, **kwargs)
        self.geometry_manager_forget = super().pack_forget

    def place(self, **kwargs):
        """
        Override the place() method.

        Args:
            **kwargs (keyword arguments): Keyword arguments for place().
        """
        self.geometry_manager_add = functools.partial(super().place, **kwargs)
        self.geometry_manager_forget = super().place_forget

def start(
        geoms=None,
        bbox=[],
        bbox_or_quit=False,
        single=False,
        format_crs_info=None,
        projpicker_db=None):
    """
    Start the GUI. Parsable geometries by geoms or queried BBox instances by
    bbox can be specified optionally. If both are given, bbox is ignored and
    only geoms is used. If single is True, it returns a single BBox instance in
    a list.

    Args:
        geoms (list or str): Parsable geometries. Defaults to None.
        bbox (list): Queried BBox instances. Defaults to [].
        bbox_or_quit (bool): Whether or not to quit when bbox queried using
            input geoms or input bbox is empty. Defaults to False.
        single (bool): Whether or not a single BBox instance is returned.
            Defaults to False.
        format_crs_info (function): User function used for formatting CRS info.
            Defaults to None in which case the default function is provided.
        projpicker_db (str): projpicker.db path. Defaults to None.

    Returns:
        list, list, list: Lists of selected BBox instances, queried BBox
        instances sorted by area, and parsed geometries.
    """
    sel_crs = []
    prev_crs_items = []
    sel_bbox = []

    doc_url = "https://projpicker.readthedocs.io/"

    tag_map = "map"
    tag_bbox = "bbox"
    tag_geoms = "geoms"
    tag_dragged_bbox = "dragged_bbox"
    tag_doc = "doc"

    zoomer = None
    zoomer_queue = queue.Queue()
    dzoom = get_dzoom()

    dragged = False
    dragging_bbox = False
    dragged_bbox = []
    drawing_bbox = False
    complete_drawing = False
    prev_xy = []
    curr_geom = []

    point_size = 4
    line_width = 2
    fill_stipple = "gray12"
    geoms_color = "blue"
    dragged_bbox_color = "green"
    sel_bbox_color = "red"

    lat, lon = get_latlon()
    zoom = get_zoom()

    def on_drag(event):
        nonlocal dragged, dragging_bbox

        if event.state & 0x4:
            # Control + B1-Motion
            latlon = osm.canvas_to_latlon(event.x, event.y)
            if not dragging_bbox:
                dragging_bbox = True
                dragged_bbox.append(latlon)
            else:
                if len(dragged_bbox) == 2:
                    del dragged_bbox[1]
                dragged_bbox.append(latlon)

                s = dragged_bbox[1][0]
                n = dragged_bbox[0][0]
                w = dragged_bbox[0][1]
                e = dragged_bbox[1][1]

                map_canvas.delete(tag_dragged_bbox)
                for xy in osm.get_bbox_xy((s, n, w, e)):
                    map_canvas.create_rectangle(xy, outline=dragged_bbox_color,
                                                width=line_width,
                                                fill=dragged_bbox_color,
                                                stipple=fill_stipple,
                                                tag=tag_dragged_bbox)
            latlon = osm.canvas_to_latlon(event.x, event.y)
            coor_label.config(text=f"{latlon[0]:.4f}, {latlon[1]:.4f} ")
        else:
            dx, dy = osm.drag(event.x, event.y, False)
            if prev_xy:
                prev_xy[0] += dx
                prev_xy[1] += dy
            draw_map(event.x, event.y)
        dragged = True

    def on_move(event):
        latlon = osm.canvas_to_latlon(event.x, event.y)
        coor_label.config(text=f"{latlon[0]:.4f}, {latlon[1]:.4f} ")
        draw_geoms(event.x, event.y)

    def on_draw(event):
        nonlocal dragged, dragging_bbox, drawing_bbox, complete_drawing

        if dragging_bbox:
            ng = len(dragged_bbox)
            s = min(dragged_bbox[0][0], dragged_bbox[ng-1][0])
            n = max(dragged_bbox[0][0], dragged_bbox[ng-1][0])
            w = dragged_bbox[0][1]
            e = dragged_bbox[ng-1][1]
            if s == n:
                n += 0.0001
            if w == e:
                e += 0.0001
            osm.zoom_to_bbox([s, n, w, e], False)
            dragged_bbox.clear()
            dragging_bbox = False
            map_canvas.delete(tag_dragged_bbox)
            draw_map(event.x, event.y)
        elif complete_drawing:
            query = ""
            geom = []
            if drawing_bbox:
                if len(curr_geom) == 2:
                    s = min(curr_geom[0][0], curr_geom[1][0])
                    n = max(curr_geom[0][0], curr_geom[1][0])
                    w = curr_geom[0][1]
                    e = curr_geom[1][1]
                    if s == n:
                        n += 0.0001
                    if w == e:
                        e += 0.0001
                    geom.extend(["bbox", [s, n, w, e]])
                    query = f"bbox {s:.4f},{n:.4f},{w:.4f},{e:.4f}"
                drawing_bbox = False
            elif len(curr_geom) == 1:
                lat, lon = curr_geom[0]
                geom.extend(["point", [lat, lon]])
                query = f"point {lat:.4f},{lon:.4f}"
            elif curr_geom:
                geom.extend(["poly", curr_geom.copy()])
                query = "poly"
                for g in curr_geom:
                    lat, lon = g
                    query += f" {lat:.4f},{lon:.4f}"
            geoms.extend(geom)
            curr_geom.clear()
            prev_xy.clear()
            if query:
                query += "\n"
                # https://stackoverflow.com/a/35855352/16079666
                # don't use .selection_get()
                ranges = query_text.tag_ranges(tk.SEL)
                if ranges:
                    name = query_text.get(*ranges).strip()
                    if name and not name.endswith(":"):
                        if not name.startswith(":"):
                            name = f":{name}:"
                        else:
                            name = ""
                    if name and ppik.geom_var_re.match(name):
                        query = query.replace(" ", f" {name} ", 1)
                    index = ranges[0].string
                else:
                    index = query_text.index(tk.INSERT)
                line, col = list(map(lambda x: int(x), index.split(".")))
                if col > 0:
                    query = "\n" + query
                    line += 1
                if ranges:
                    query_text.replace(*ranges, query)
                else:
                    query_text.insert(tk.INSERT, query)
                query_text.mark_set(tk.INSERT, f"{line+1}.0")
                notebook.select(query_frame)
                draw_geoms()
        elif not dragged:
            # https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/event-handlers.html
            if event.state & 0x4:
                # Control + ButtonRelease-1
                drawing_bbox = True
                curr_geom.clear()
            elif drawing_bbox and len(curr_geom) == 2:
                del curr_geom[1]
            latlon = list(osm.canvas_to_latlon(event.x, event.y))
            if not drawing_bbox:
                if prev_xy:
                    latlon[1] = adjust_lon(prev_xy[0], event.x,
                                           curr_geom[len(curr_geom)-1][1],
                                           latlon[1])
                prev_xy.clear()
                prev_xy.extend([event.x, event.y])
            curr_geom.append(latlon)

        dragged = False
        complete_drawing = False

    def on_complete_drawing(event):
        nonlocal complete_drawing

        # XXX: sometimes, double-click events occur for both clicks and there
        # is no reliable way to register the first click only using
        # complete_drawing; a hacky way to handle such cases
        if not curr_geom:
            curr_geom.append(osm.canvas_to_latlon(event.x, event.y))
            prev_xy = [event.x, event.y]
        complete_drawing = True

    def on_cancel_drawing(event):
        nonlocal drawing_bbox

        drawing_bbox = False
        curr_geom.clear()
        prev_xy.clear()
        draw_geoms()

    def on_resize(event):
        osm.resize(event.width, event.height)
        x = root.winfo_pointerx() - root.winfo_rootx()
        y = root.winfo_pointery() - root.winfo_rooty()
        draw_map(x, y)

    def on_select_crs(event):
        nonlocal prev_crs_items

        curr_crs_items = crs_treeview.selection()

        if single:
            prev_crs_items.clear()

        curr_crs_item = None
        if len(curr_crs_items) > len(prev_crs_items):
            # selected a new crs
            curr_crs_item = list(set(curr_crs_items) - set(prev_crs_items))[0]
            prev_crs_items.append(curr_crs_item)
        elif len(curr_crs_items) < len(prev_crs_items):
            # deselected an existing crs
            item = list(set(prev_crs_items) - set(curr_crs_items))[0]
            del prev_crs_items[prev_crs_items.index(item)]
            l = len(prev_crs_items)
            if l > 0:
                curr_crs_item = prev_crs_items[l-1]
        elif curr_crs_items:
            prev_crs_items.clear()
            prev_crs_items.extend(curr_crs_items)
            curr_crs_item = prev_crs_items[len(prev_crs_items)-1]

        if wkt_frame:
            wkt_text.delete("1.0", tk.END)
        crs_info_text.delete("1.0", tk.END)
        sel_bbox.clear()

        if curr_crs_item:
            crs_id = crs_treeview.item(curr_crs_item)["values"][0]
            b = find_bbox(crs_id, bbox)

            if wkt_frame:
                wkt = pyproj.CRS(f"{b.crs_auth_name}:{b.crs_code}").to_wkt(
                        pretty=True)
                wkt_text.insert(tk.END, wkt)
                notebook.select(wkt_frame)

            crs_info = create_crs_info(b, format_crs_info)
            crs_info_text.insert(tk.END, crs_info)

            s, n, w, e = b.south_lat, b.north_lat, b.west_lon, b.east_lon
            s, n, w, e = osm.zoom_to_bbox([s, n, w, e])
            sel_bbox.extend([s, n, w, e])

            if not wkt_frame:
                notebook.select(crs_info_frame)
        draw_geoms()
        draw_bbox()

    def draw_map(x, y):
        osm.draw()
        draw_geoms(x, y)
        draw_bbox()

    def draw_geoms(x=None, y=None):
        all_geoms = geoms.copy()
        if curr_geom and x and y:
            latlon = list(osm.canvas_to_latlon(x, y))

            g = curr_geom.copy()
            g.append(latlon)

            if drawing_bbox:
                ng = len(g)
                s = min(g[0][0], g[ng-1][0])
                n = max(g[0][0], g[ng-1][0])
                w = g[0][1]
                e = g[ng-1][1]
                if s == n:
                    n += 0.0001
                if w == e:
                    e += 0.0001
                all_geoms.extend(["bbox", [s, n, w, e]])
            elif g:
                if prev_xy:
                    latlon[1] = adjust_lon(prev_xy[0], x,
                                           curr_geom[len(curr_geom)-1][1],
                                           latlon[1])
                g.append(latlon)
                all_geoms.extend(["poly", g])

        map_canvas.delete(tag_geoms)

        point_half_size = point_size // 2
        geom_type = "point"
        g = 0
        ngeoms = len(all_geoms)
        while g < ngeoms:
            geom = all_geoms[g]
            if geom in ("point", "poly", "bbox"):
                geom_type = geom
                g += 1
                geom = all_geoms[g]
            if type(geom) == list:
                if geom_type == "point":
                    for xy in osm.get_xy([geom]):
                        x, y = xy[0]
                        oval = (x - point_half_size, y - point_half_size,
                                x + point_half_size, y + point_half_size)
                        map_canvas.create_oval(oval, outline=geoms_color,
                                               width=line_width,
                                               fill=geoms_color, tag=tag_geoms)
                elif geom_type == "poly":
                    for xy in osm.get_xy(geom):
                        map_canvas.create_polygon(xy, outline=geoms_color,
                                                  width=line_width,
                                                  fill=geoms_color,
                                                  stipple=fill_stipple,
                                                  tag=tag_geoms)
                else:
                    for xy in osm.get_bbox_xy(geom):
                        map_canvas.create_rectangle(xy, outline=geoms_color,
                                                    width=line_width,
                                                    fill=geoms_color,
                                                    stipple=fill_stipple,
                                                    tag=tag_geoms)
            g += 1

    def draw_bbox():
        map_canvas.delete(tag_bbox)
        for xy in osm.get_bbox_xy(sel_bbox):
            map_canvas.create_rectangle(xy, outline=sel_bbox_color,
                                        width=line_width, fill=sel_bbox_color,
                                        stipple=fill_stipple, tag=tag_bbox)

    def zoom_map(x, y, dz, state):
        def zoom(x, y, dz, cancel_event):
            if not cancel_event.wait(0.01) and osm.redownload():
                # ready to draw map; how? event_generate()? maybe...
                # https://www.tcl.tk/man/tcl8.7/TkCmd/event.html#M7
                # https://mail.python.org/pipermail/python-list/2003-December/197985.html
                # https://bytes.com/topic/python/answers/735362-tkinter-threads-communication
                # however...
                # http://stupidpythonideas.blogspot.com/2013/10/why-your-gui-app-freezes.html?view=classic
                # https://bugs.python.org/issue33412
                # when cancel_event is set after cancel_event.wait() but before
                # event_generate(), event_generate() hangs and zoomer.join()
                # never returns; I tried cancel_event.wait() here again, but it
                # still hung sometimes with when=tail
                # map_canvas.event_generate("<<Zoomed>>", when="tail")

                # let's use queue and after_idle()
                zoomer_queue.put((draw_map, x, y))

        def check_zoomer():
            nonlocal zoomer

            try:
                draw_map, x, y = zoomer_queue.get_nowait()
            except queue.Empty:
                zoomer.checker = map_canvas.after_idle(check_zoomer)
            else:
                draw_map(x, y)

        # https://stackoverflow.com/a/63305873/16079666
        # https://stackoverflow.com/a/26703844/16079666
        # https://wiki.tcl-lang.org/page/Tcl+event+loop
        # first, I tried multi-threading in the OpenStreetMap class, but
        # map_canvas flickered too much; according to the above references,
        # tkinter doesn't like threading, so I decided to use its native
        # after() and after_cancel() to keep only the last zooming event;
        # however, once osm.zoom() started, there was no way to cancel
        # downloading, so I separated GUI code from download code in the
        # OpenStreetMap class and split the draw_map() function into
        # download_map() and draw_map()
        nonlocal zoomer

        if state & 0x4:
            # Control + MouseWheel
            if dz > 0:
                geoms_bbox = calc_geoms_bbox(geoms)
                if None not in geoms_bbox:
                    osm.zoom_to_bbox(geoms_bbox, False)
            else:
                osm.zoom(x, y, osm.z_min - osm.z, False)
            draw_map(x, y)
            return

        if zoomer:
            # cancel the previous zoomer thread
            zoomer.cancel_event.set()

            # cancel the previous map download; why do I set it here instead of
            # inside the zoommer like "if cancel_event, osm.cancel = True else
            # osm.zoom"? if it's inside the zoomer, unless timeout is long
            # enough to wait until another consecutive zoom event occurs,
            # osm.zoom() in the else block will start and once it started,
            # osm.cancel = True in the if block will never happen because the
            # zoomer runs only once; in other words, osm.zoom() that already
            # started will never see osm.cancel = True; to avoid this problem,
            # osm.cancel is set outside the zoomer
            osm.cancel = True
            zoomer.join()
            osm.cancel = False
            map_canvas.after_cancel(zoomer.checker)

            cancel_event = zoomer.cancel_event
            # need to clear to reuse it
            cancel_event.clear()
        else:
            cancel_event = threading.Event()

        osm.rescale(x, y, dz)
        zoomer = threading.Thread(target=zoom, args=(x, y, dz, cancel_event))
        zoomer.cancel_event = cancel_event
        zoomer.checker = map_canvas.after_idle(check_zoomer)
        zoomer.start()

    def import_query():
        try:
            f = filedialog.askopenfile(title="Import query",
                                       filetypes=file_types)
            if f:
                query_text.delete("1.0", tk.END)
                query_text.insert(tk.INSERT, f.read())
                f.close()
        except Exception as e:
            messagebox.showerror("Import query error", e)

    def export_query():
        try:
            f = filedialog.asksaveasfile(title="Export query",
                                         filetypes=file_types)
            if f:
                # https://stackoverflow.com/a/66753640/16079666
                # text widget always appends a new line at the end
                query_to_export = query_text.get("1.0", "end-1c")
                f.write(query_to_export)
                f.close()
        except Exception as e:
            messagebox.showerror("Export query error", e)

    def query():
        nonlocal bbox

        query = query_text.get("1.0", tk.END)
        geoms.clear()
        log_text.delete("1.0", tk.END)
        try:
            geoms.extend(ppik.parse_mixed_geoms(query))
            bbox = ppik.query_mixed_geoms(geoms, projpicker_db)
        except Exception as e:
            log_text.insert(tk.END, e)
            notebook.select(log_frame)

        populate_crs_list(bbox)
        search()
        draw_geoms()

    def populate_crs_list(bbox):
        crs_treeview.delete(*crs_treeview.get_children())
        for b in bbox:
            crs_treeview.insert("", tk.END, values=(
                                f"{b.crs_auth_name}:{b.crs_code}", b.crs_name))
        sel_bbox.clear()
        draw_bbox()

    def search():
        text = [x.strip() for x in search_text.get().split(";")]
        if "".join(text):
            filt_bbox = ppik.search_bbox(bbox, text)
            populate_crs_list(filt_bbox)
            prev_crs_items.clear()

    def select():
        nonlocal sel_crs

        for item in crs_treeview.selection():
            sel_crs.append(crs_treeview.item(item)["values"][0])
        root.destroy()

    # parse geometries if given
    if geoms:
        geoms, query_string = parse_geoms(geoms)
        bbox = ppik.query_mixed_geoms(geoms, projpicker_db)
    else:
        geoms = []
        query_string = ""

    if bbox_or_quit and not bbox:
        return [], bbox, geoms

    #####
    # GUI

    # root window
    root = tk.Tk(className="ProjPickerGUI")
    root_width = 800
    root_height = root_width
    root.geometry(f"{root_width}x{root_height}")
    root.title("ProjPicker tkGUI")
    # https://stackoverflow.com/a/5871414/16079666
    root.bind_class("Text", "<Control-a>",
                    lambda e: e.widget.tag_add(tk.SEL, "1.0", tk.END))

    ###########
    # top frame
    map_canvas_width = root_width
    map_canvas_height = root_height // 2

    map_canvas = tk.Canvas(root, height=map_canvas_height)
    map_canvas.pack(fill=tk.BOTH)

    osm = OpenStreetMap(
            lambda width, height: map_canvas.delete(tag_map),
            lambda image: map_canvas.tag_lower(tag_map),
            lambda data: tk.PhotoImage(data=data),
            lambda image, tile, x, y:
                map_canvas.create_image(x, y, anchor=tk.NW, image=tile,
                                        tag=tag_map),
            lambda tile, dz: tile.zoom(2**abs(dz)) if dz > 0 else
                             tile.subsample(2**abs(dz)),
            map_canvas_width, map_canvas_height,
            lat, lon, zoom, ppik.is_verbose())

    map_canvas.bind("<ButtonPress-1>", lambda e: osm.grab(e.x, e.y))
    map_canvas.bind("<B1-Motion>", on_drag)
    # Linux
    # https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/event-types.html
    map_canvas.bind("<Button-4>", lambda e: zoom_map(e.x, e.y, dzoom, e.state))
    map_canvas.bind("<Button-5>", lambda e: zoom_map(e.x, e.y, -dzoom,
                                                     e.state))
    # Windows and macOS
    # https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/event-types.html
    map_canvas.bind("<MouseWheel>",
                    lambda e: zoom_map(e.x, e.y,
                                       dzoom if e.delta > 0 else -dzoom,
                                       e.state))
    map_canvas.bind("<Motion>", on_move)
    map_canvas.bind("<ButtonRelease-1>", on_draw)
    map_canvas.bind("<Double-Button-1>", on_complete_drawing)
    map_canvas.bind("<ButtonRelease-3>", on_cancel_drawing)
    map_canvas.bind("<Double-Button-3>", lambda e: geoms.clear())
    map_canvas.bind("<Configure>", on_resize)

    # draw geometries if given
    if geoms:
        geoms_bbox = calc_geoms_bbox(geoms)
        if None not in geoms_bbox:
            osm.zoom_to_bbox(geoms_bbox)
        draw_geoms()

    ##############
    # bottom frame
    bottom_frame_height = root_height - map_canvas_height
    bottom_frame = ttk.Frame(root, height=bottom_frame_height)
    bottom_frame.pack_propagate(False)
    bottom_frame.pack(side=tk.BOTTOM, fill=tk.BOTH, expand=True)

    ###################
    # bottom-left frame
    bottom_left_frame_width = root_width // 2
    bottom_left_frame = ttk.Frame(bottom_frame, width=bottom_left_frame_width)
    bottom_left_frame.pack_propagate(False)
    bottom_left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

    #######################
    # bottom-left-top frame
    bottom_left_top_frame = ttk.Frame(bottom_left_frame)
    bottom_left_top_frame.pack(fill=tk.BOTH, expand=True)

    # list of CRSs
    id_width = 100
    name_width = bottom_left_frame_width - id_width - 15
    crs_cols = {"ID": id_width, "Name": name_width}

    crs_treeview = ttk.Treeview(
            bottom_left_top_frame, columns=list(crs_cols.keys()),
            show="headings", selectmode=tk.BROWSE if single else tk.EXTENDED)
    for name, width in crs_cols.items():
        crs_treeview.heading(name, text=name)
        crs_treeview.column(name, width=width)

    populate_crs_list(bbox)

    crs_treeview.bind("<<TreeviewSelect>>", on_select_crs)
    crs_treeview.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

    # vertical scroll bar for CRS list
    crs_treeview_vscrollbar = AutoScrollbar(bottom_left_top_frame)
    crs_treeview_vscrollbar.config(command=crs_treeview.yview)
    crs_treeview_vscrollbar.pack(side=tk.LEFT, fill=tk.Y)
    crs_treeview.config(yscrollcommand=crs_treeview_vscrollbar.set)

    ##########################
    # bottom-left-middle frame

    # horizontal scroll bar for CRS list
    crs_treeview_hscrollbar = AutoScrollbar(bottom_left_frame,
                                        orient=tk.HORIZONTAL)
    crs_treeview_hscrollbar.config(command=crs_treeview.xview)
    crs_treeview_hscrollbar.pack(fill=tk.X)
    crs_treeview.config(xscrollcommand=crs_treeview_hscrollbar.set)

    # text for search
    search_text = tk.Entry(bottom_left_frame)
    search_text.pack(fill=tk.X, ipady=4)
    search_text.bind("<KeyRelease>", lambda e: search())

    ####################
    # bottom-right frame
    notebook_width = root_width - bottom_left_frame_width
    notebook = ttk.Notebook(bottom_frame, width=notebook_width)
    notebook.pack_propagate(False)

    query_frame = ttk.Frame(notebook)
    notebook.add(query_frame, text="Query")

    if pyproj:
        wkt_frame = ttk.Frame(notebook)
        notebook.add(wkt_frame, text="WKT")
    else:
        wkt_frame = None

    crs_info_frame = ttk.Frame(notebook)
    notebook.add(crs_info_frame, text="CRS Info")

    log_frame = ttk.Frame(notebook)
    notebook.add(log_frame, text="Log")

    help_frame = ttk.Frame(notebook)
    notebook.add(help_frame, text="Help")

    notebook.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

    #############
    # query frame
    query_top_frame = ttk.Frame(query_frame)
    query_top_frame.pack(fill=tk.BOTH, expand=True)

    # text for query
    query_text = tk.Text(query_top_frame, width=20, height=1, wrap=tk.NONE)
    query_text.insert(tk.INSERT, query_string)
    query_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

    # pop-up menu
    file_types = (("ProjPicker query files", "*.ppik"), ("All files", "*.*"))
    menu = tk.Menu(query_text, tearoff=0)
    menu.add_command(label="Import query", command=import_query)
    menu.add_command(label="Export query", command=export_query)
    query_text.bind("<Button-3>", lambda e: menu.post(e.x_root, e.y_root))
    # left click or escape anywhere to close the menu
    root.bind("<Button-1>", lambda e: menu.unpost())
    root.bind("<Escape>", lambda e: menu.unpost())

    # vertical scroll bar for query
    query_vscrollbar = AutoScrollbar(query_top_frame)
    query_vscrollbar.config(command=query_text.yview)
    query_vscrollbar.pack(side=tk.LEFT, fill=tk.Y)
    query_text.config(yscrollcommand=query_vscrollbar.set)

    # horizontal scroll bar for query
    query_hscrollbar = AutoScrollbar(query_frame, orient=tk.HORIZONTAL)
    query_hscrollbar.config(command=query_text.xview)
    query_hscrollbar.pack(fill=tk.X)
    query_text.config(xscrollcommand=query_hscrollbar.set)

    query_bottom_frame = ttk.Frame(query_frame)
    query_bottom_frame.pack(fill=tk.BOTH)

    # buttons
    ttk.Button(query_bottom_frame, text="Query", command=query).pack(
               side=tk.LEFT, expand=True)
    ttk.Button(query_bottom_frame, text="Cancel", command=root.destroy).pack(
               side=tk.LEFT, expand=True)

    ###########
    # WKT frame

    if wkt_frame:
        wkt_top_frame = ttk.Frame(wkt_frame)
        wkt_top_frame.pack(fill=tk.BOTH, expand=True)

        # text for WKT
        wkt_text = tk.Text(wkt_top_frame, width=20, height=1, wrap=tk.NONE)
        wkt_text.bind("<Key>", lambda e: None if e.state & 0x4 else "break")
        wkt_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        # vertical scroll bar for WKT
        wkt_vscrollbar = AutoScrollbar(wkt_top_frame)
        wkt_vscrollbar.config(command=wkt_text.yview)
        wkt_vscrollbar.pack(side=tk.LEFT, fill=tk.Y)
        wkt_text.config(yscrollcommand=wkt_vscrollbar.set)

        # horizontal scroll bar for WKT
        wkt_hscrollbar = AutoScrollbar(wkt_frame, orient=tk.HORIZONTAL)
        wkt_hscrollbar.config(command=wkt_text.xview)
        wkt_hscrollbar.pack(fill=tk.X)
        wkt_text.config(xscrollcommand=wkt_hscrollbar.set)

        wkt_bottom_frame = ttk.Frame(wkt_frame)
        wkt_bottom_frame.pack(fill=tk.BOTH)

        # buttons
        ttk.Button(wkt_bottom_frame, text="Select", command=select).pack(
                   side=tk.LEFT, expand=True)
        ttk.Button(wkt_bottom_frame, text="Cancel",
                   command=root.destroy).pack(side=tk.LEFT, expand=True)

    ################
    # CRS info frame

    # text for CRS info
    crs_info_text = tk.Text(crs_info_frame, width=20, height=1, wrap=tk.NONE)
    crs_info_text.bind("<Key>", lambda e: None if e.state & 0x4 else "break")
    crs_info_text.pack(fill=tk.BOTH, expand=True)

    # horizontal scroll bar for CRS info
    crs_info_hscrollbar = AutoScrollbar(crs_info_frame, orient=tk.HORIZONTAL)
    crs_info_hscrollbar.config(command=crs_info_text.xview)
    crs_info_hscrollbar.pack(fill=tk.X)
    crs_info_text.config(xscrollcommand=crs_info_hscrollbar.set)

    crs_info_bottom_frame = ttk.Frame(crs_info_frame)
    crs_info_bottom_frame.pack(fill=tk.BOTH)

    # buttons
    ttk.Button(crs_info_bottom_frame, text="Select", command=select).pack(
               side=tk.LEFT, expand=True)
    ttk.Button(crs_info_bottom_frame, text="Cancel",
               command=root.destroy).pack(side=tk.LEFT, expand=True)

    ###########
    # log frame
    log_top_frame = ttk.Frame(log_frame)
    log_top_frame.pack(fill=tk.BOTH, expand=True)

    # text for log
    log_text = tk.Text(log_top_frame, width=20, height=1, wrap=tk.NONE)
    log_text.bind("<Key>", lambda e: None if e.state & 0x4 else "break")
    log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

    # vertical scroll bar for log
    log_vscrollbar = AutoScrollbar(log_top_frame)
    log_vscrollbar.config(command=log_text.yview)
    log_vscrollbar.pack(side=tk.LEFT, fill=tk.Y)
    log_text.config(yscrollcommand=log_vscrollbar.set)

    # horizontal scroll bar for log
    log_hscrollbar = AutoScrollbar(log_frame, orient=tk.HORIZONTAL)
    log_hscrollbar.config(command=log_text.xview)
    log_hscrollbar.pack(fill=tk.X)
    log_text.config(xscrollcommand=log_hscrollbar.set)

    ############
    # help frame

    # text for help
    help_text = tk.Text(help_frame, width=20, height=1, wrap=tk.NONE)
    help_text.insert(tk.END, textwrap.dedent(f"""\
            Pan:                        Left drag
            Zoom:                       Scroll
            Zoom to geometries:         Ctrl + scroll up
            Zoom to the world:          Ctrl + scroll down
            Draw/zoom to a bbox:        Ctrl + left drag
            Draw a point:               Double left click
            Start drawing a poly:       Left click
            Start drawing a bbox:       Ctrl + left click
            Complete a poly/bbox:       Double left click
            Cancel drawing a poly/bbox: Right click
            Clear geometries:           Double right click

            To define a geometry variable, type and highlight
            a name in the query builder, then create a geometry.

            Query files (*.ppik) can be imported or exported
            by right clicking on the query builder.

            Search words can be a CRS ID or separated by
            a semicolon to search multiple fields using
            the logical AND operator.

            See {doc_url} to learn more."""))
    help_text.tag_add(tag_doc, "end-1l+4c", "end-16c")
    help_text.tag_config(tag_doc, foreground="blue", underline=True)
    help_text.tag_bind(tag_doc, "<Enter>",
                       lambda e: help_text.config(cursor="hand2"))
    help_text.tag_bind(tag_doc, "<Leave>",
                       lambda e: help_text.config(cursor=""))
    help_text.tag_bind(tag_doc, "<Button-1>",
                       lambda e: webbrowser.open(doc_url))
    help_text.config(state=tk.DISABLED)
    help_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

    help_vscrollbar = AutoScrollbar(help_frame)
    help_vscrollbar.config(command=help_text.yview)
    help_vscrollbar.pack(side=tk.RIGHT, fill=tk.Y)
    help_text.config(yscrollcommand=help_vscrollbar.set)

    # label for coordinates
    coor_label = ttk.Label(notebook)
    coor_label.place(relx=1, rely=0, anchor=tk.NE)

    #########
    # run GUI
    root.mainloop()

    return [find_bbox(crs_id, bbox) for crs_id in sel_crs], bbox, geoms


from typing import List

class Solution:
    def palindromePairs(self, words: List[str]) -> List[List[int]]:
        fw_dict = {}
        ls_dict = {}
        rs_dict = {}

        self_re_list = []
        null_list = []

        res_pairs = []

        for i in range(len(words)):
            word = words[i]
            revers_word = word[::-1]
            if revers_word in fw_dict:
                for j in fw_dict[revers_word]:
                    res_pairs.append([j, i])
                    res_pairs.append([i, j])
            if revers_word in ls_dict:
                for j in ls_dict[revers_word]:
                    res_pairs.append([j, i])
            if revers_word in rs_dict:
                for j in rs_dict[revers_word]:
                    res_pairs.append([i, j])

            if word in fw_dict:
                fw_dict[word].append(i)
            else:
                fw_dict[word] = [i]

            if len(word) == 0:
                null_list.append(i)
            elif word == revers_word:
                self_re_list.append(i)

            for j in range(1, len(word)):
                ls = word[:j]
                rs = word[j:]
                if ls == ls[::-1]:
                    if rs[::-1] in fw_dict:
                        for k in fw_dict[rs[::-1]]:
                            res_pairs.append([k, i])
                    if rs in rs_dict:
                        rs_dict[rs].append(i)
                    else:
                        rs_dict[rs] = [i]
                if rs == rs[::-1]:
                    if ls[::-1] in fw_dict:
                        for k in fw_dict[ls[::-1]]:
                            res_pairs.append([i, k])
                    if ls in ls_dict:
                        ls_dict[ls].append(i)
                    else:
                        ls_dict[ls] = [i]
        for i in null_list:
            for j in self_re_list:
                res_pairs.append([i, j])
                res_pairs.append([j, i])
        return res_pairs


import uuid

from django.contrib.auth.models import (
    AbstractUser,
    _user_get_permissions,
)
from django.db import models
from django.utils.translation import gettext_lazy as _

# Create your models here.
class PermissionManager(models.Manager):
    use_in_migrations = True

    def get_by_natural_key(self, codename, *args, **kwargs):
        return self.get(codename=codename)

class Permission(models.Model):
    name = models.CharField(_("name"), max_length=255)
    codename = models.SlugField(_("codename"), unique=True)
    app_label = models.CharField(max_length=40)

    objects = PermissionManager()

    class Meta:
        verbose_name = _("permission")
        verbose_name_plural = _("permissions")
        ordering = ["codename"]

    def __str__(self):
        return self.name

    def natural_key(self):
        return (self.codename,)

class GroupManager(models.Manager):
    use_in_migrations = True

    def get_by_natural_key(self, name):
        return self.get(name=name)

class Group(models.Model):
    name = models.CharField(_("name"), max_length=150, unique=True)
    permissions = models.ManyToManyField(
        Permission, verbose_name=_("permissions"), blank=True
    )

    objects = GroupManager()

    class Meta:
        verbose_name = _("group")
        verbose_name_plural = _("groups")

    def __str__(self):
        return self.name

    def natural_key(self):
        return (self.name,)

class PermissionsMixin(models.Model):
    is_superuser = models.BooleanField(
        _("superuser status"),
        default=False,
        help_text=_(
            "Designates that this user has all permissions without "
            "explicitly assigning them."
        ),
    )
    groups = models.ManyToManyField(
        Group,
        verbose_name=_("groups"),
        blank=True,
        help_text=_(
            "The groups this user belongs to. A user will get all permissions "
            "granted to each of their groups."
        ),
        related_name="user_group_set",
        related_query_name="user_group",
    )
    user_permissions = models.ManyToManyField(
        Permission,
        verbose_name=_("user permissions"),
        blank=True,
        help_text=_("Specific permissions for this user."),
        related_name="user_perm_set",
        related_query_name="user_perm",
    )

    class Meta:
        abstract = True

    def get_user_permissions(self, obj=None):
        """
        Return a list of permission strings that this user has directly.
        Query all available auth backends. If an object is passed in,
        return only permissions matching this object.
        """
        return _user_get_permissions(self, obj, "user")

    def get_group_permissions(self):
        """
        Return a list of permission strings that this user has through their
        groups. Query all available auth backends. If an object is passed in,
        return only permissions matching this object.
        """
        return [
            perm.codename
            for perm in Permission.objects.filter(group__in=self.groups.all())
        ]

    def get_all_permissions(self):
        return [
            perm.codename for perm in self.user_permissions.all()
        ] + self.get_group_permissions()

    def has_perm(self, perm):
        """
        Return True if the user has the specified permission. Query all
        available auth backends, but return immediately if any backend returns
        True. Thus, a user who has permission from a single auth backend is
        assumed to have permission in general. If an object is provided, check
        permissions for that object.
        """
        # Active superusers have all permissions.
        if self.is_active and self.is_superuser:
            return True

        # Otherwise we need to check the backends.
        return perm in self.get_all_permissions()

    def has_perms(self, perm_list):
        """
        Return True if the user has each of the specified permissions. If
        object is passed, check if the user has all required perms for it.
        """
        if self.is_active and self.is_superuser:
            return True
        return all(self.has_perm(perm) for perm in perm_list)

class Model(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)

    def __str__(self):
        return str(self.id)

    class Meta:
        abstract = True

class User(PermissionsMixin, AbstractUser, Model):
    class Meta:
        ordering = ["first_name", "last_name"]

    def get_full_name(self):
        full_name = super().get_full_name()

        if full_name == "":
            return self.get_username()
        else:
            return full_name


import torch
import torchvision
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from torchvision import datasets
from torchvision import transforms
from torchvision.utils import save_image

import numpy as np
import scipy.misc

'''
is_available() function checks for tha availability of CUDA enabled GPU for parallelization.
Device represents the device on which a torch.Tensor is allotted.
A torch.Tensor's device can be accessed via Tensor.device property.
to() function in pytorch performs conversion of tensor datatype or device conversion.
'''

if torch.cuda.is_available():
    device = "cuda:0"
else:
    device = "cpu"

def to_cuda(x):
    return x.to(device)

'''
one_hot_encoder() function computes one hot encoding of the output label of the digit
torch.LongTensor() creates a tensor of the specified shape
zero_() fills the tensor with zeros
scatter_(dim,index,src) function writtes the values from src (1) to self(one_hot)
at the index specified by the index tensor (y).
'''
def one_hot_encoder(y, num_classes=10):
    one_hot = torch.LongTensor(y.size(0), num_classes).zero_()
    one_hot.scatter_(1,y,1)
    return one_hot

'''
Printing the out images from the generator
Print 100 output images in 10x10 grid
torch.cat() concatenates the given sequence of tensros in the given dimension.
For 10x10 grid of output images, 10 images are concatenated in vertical dimension (stored in line_img)
and all the vertical line of images are concatenated horizontally (stored in all_img).
'''
def get_output_image(latent_dim=100):
    for num in range(10):
        for i in range(10):
            z = to_cuda(torch.randn(1, latent_dim))
            out = G(z)
            line_img = torch.cat((line_img, out.view(28, 28)), dim=1) if i > 0 else out.view(28, 28)
        all_img = torch.cat((all_img, line_img), dim=0) if num > 0 else line_img
    img = all_img.cpu().data.numpy()
    return img

'''
Discriminator architecture as per the paper.
The class inherits from nn.Module whose forward function is overridden as per the model.
The model is defined in the constructor and the forward propagation is described in forward()
First three layers are a bloack consisting of Convolution, Batch Normalization and LeakyRelu functions.
Last layer is a fully connected layer with sigmoid activation.

Discriminator input = image of size (28,28)
Discriminator output = 0 or 1
'''
class Discriminator(nn.Module):

    def __init__(self, in_channels = 1, num_classes = 1):

        super(Discriminator, self).__init__()

        self.layer1 = nn.Sequential(
            nn.Conv2d(in_channels, 128, 3, stride = 2, padding = 1),
            nn.BatchNorm2d(128),
            nn.LeakyReLU(0.2)
        )

        self.layer2 = nn.Sequential(
            nn.Conv2d(128, 256, 3, stride = 2, padding = 1),
            nn.BatchNorm2d(256),
            nn.LeakyReLU(0.2)
        )

        self.layer3 = nn.Sequential(
            nn.Conv2d(256,512, 3, stride = 2, padding = 1),
            nn.BatchNorm2d(512),
            nn.LeakyReLU(0.2),
            nn.AvgPool2d(4)
        )

        self.layer4 = nn.Sequential(
            nn.Linear(512,1),
            nn.Sigmoid()
        )

    def forward(self, x, y = None):
        out = self.layer1(x)
        out = self.layer2(out)
        out = self.layer3(out)
        out = out.view(out.size(0), -1)
        out = self.layer4(out)

        return out
'''
Generator architecture as per the paper.
The class inherits from nn.Module whose forward function is overridden as per the model.
The model is defined in the constructor and the forward propagation is described in forward()
Last three layers are a bloack consisting of Convolution, Batch Normalization and ReLu functions.
First layer is a fully connected layer with ReLU activation.

Generator input: Random noise with input_size = 100
Generator output: Vector of size (784,1) which is reshaped as (28,28) for image generation
'''

class Generator(nn.Module):

    def __init__(self, input_size=100, label_size=10, num_classes=784):
        super(Generator, self).__init__()

        self.layer1 = nn.Sequential(
            nn.Linear(input_size, 4*4*512),
            nn.ReLU(),
        )
        self.layer2 = nn.Sequential(
            nn.ConvTranspose2d(512, 256, 3, stride=2, padding=1),
            nn.BatchNorm2d(256),
            nn.ReLU(),
        )
        self.layer3 = nn.Sequential(
            nn.ConvTranspose2d(256, 128, 4, stride=2, padding=1),
            nn.BatchNorm2d(128),
            nn.ReLU(),
        )
        self.layer4 = nn.Sequential(
            nn.ConvTranspose2d(128, 1, 4, stride=2, padding=1),
            nn.Tanh(),
        )

    def forward(self, x, y=None):
        x = x.view(x.size(0), -1)
        out = self.layer1(x)
        out = out.view(out.size(0), 512, 4, 4)
        out = self.layer2(out)
        out = self.layer3(out)
        out = self.layer4(out)
        return out

# Creating object of discriminator and generator
D = to_cuda(Discriminator())
G = to_cuda(Generator())

# Dataloading and proprocessing
'''
transforms.Compose() composes several transforms of the data together
transforms.ToTensor() converts PIL image to numpy array
transforms.Normalize() normailizes the images with the mean and std along each channel
MNIST dataset loader is predefined in pytorch
'''
transform = transforms.Compose([transforms.ToTensor(),
                                transforms.Normalize(mean=(0.5, 0.5, 0.5),
                                std=(0.5, 0.5, 0.5))]
)

mnist = datasets.MNIST(root = 'MNIST-data/', train = True,
                       transform = transform, download = False)

batch_size = 128

data_loader = DataLoader(dataset=mnist, batch_size = batch_size,
                         shuffle = True, drop_last = True)
'''
Binary Cross entropy loss is defined for training the Discriminator and generator with Adam optimizer.
Discriminator is trained to output 1 for real images and 0 for generator output images.
Generator is trained to output images such that discriminator outputs 1 for those images.
'''
criterion = nn.BCELoss()
D_opt = torch.optim.Adam(D.parameters(), lr = 0.0002)
G_opt = torch.optim.Adam(G.parameters(), lr = 0.0002)

# Defining parameters and hyperparameters
max_epoch = 11
step = 0
n_critic = 1 # Number of time generator is trained for every iteration of discriminator training in every epoch
n_noise = 100 # Latent dimension of random noise fed as input to generator

D_labels = to_cuda(torch.ones(batch_size))
D_fakes = to_cuda(torch.zeros(batch_size))

# Training Loop
'''
idx, (images, labels) store the index, input images and output labels of every image in the batch

'''
for epoch in range(max_epoch):
    for idx, (images, labels) in enumerate(data_loader):
        step += 1
        # Training Discriminator
        x = to_cuda(images)
        x_outputs = D(x)
        D_x_loss = criterion(x_outputs, D_labels) #Training discriminator to output 1 for real image

        z = to_cuda(torch.randn(batch_size, n_noise))
        z_outputs = D(G(z))
        D_z_loss = criterion(z_outputs, D_fakes) #Training discriminator to output 0 for generator output image
        D_loss = D_x_loss + D_z_loss

        D.zero_grad() # make the gradients zero for every batch before backpropagation
        D_loss.backward() # Evaluate the gradients with binary cross-entropy loss
        D_opt.step() # Backpropagation with Adam optimizer to update discriminator weights for every layer

        if step % n_critic == 0:
            # Training Generator
            z = to_cuda(torch.randn(batch_size, n_noise))
            z_outputs = D(G(z))
            G_loss = criterion(z_outputs, D_labels)

            D.zero_grad()
            G.zero_grad()
            G_loss.backward()
            G_opt.step() # Backpropagation with Adam optimizer to update generator weights for every layer

        # Print Losses afer every 1000 batch processing
        if step % 1000 == 0:
            print('Epoch: {}/{}, Step: {}, D Loss: {}, G Loss: {}'.format(epoch, max_epoch, step, D_loss.data[0], G_loss.data[0]))

        # Saving output images after every 5 epochs
        if epoch % 5 == 0:
            G.eval()
            img = get_output_image()
            scipy.misc.imsave('sample/epoch_{}_type1.jpg'.format(epoch), img)
            G.train()


"""
    GraphCSPN: Geometry-Aware Depth Completion via Dynamic GCNs

    European Conference on Computer Vision (ECCV) 2022

    The code is based on https://github.com/zzangjinsun/NLSPN_ECCV20
"""

import numpy as np
import torch
import torch.nn as nn
import torchvision

def conv_bn_relu(ch_in, ch_out, kernel, stride=1, padding=0, bn=True,
                 relu=True):
    assert (kernel % 2) == 1, \
        'only odd kernel is supported but kernel = {}'.format(kernel)

    layers = []
    layers.append(nn.Conv2d(ch_in, ch_out, kernel, stride, padding,
                            bias=not bn))
    if bn:
        layers.append(nn.BatchNorm2d(ch_out))
    if relu:
        layers.append(nn.LeakyReLU(0.2, inplace=True))

    layers = nn.Sequential(*layers)

    return layers

def convt_bn_relu(ch_in, ch_out, kernel, stride=1, padding=0, output_padding=0,
                  bn=True, relu=True):
    assert (kernel % 2) == 1, \
        'only odd kernel is supported but kernel = {}'.format(kernel)

    layers = []
    layers.append(nn.ConvTranspose2d(ch_in, ch_out, kernel, stride, padding,
                                     output_padding, bias=not bn))
    if bn:
        layers.append(nn.BatchNorm2d(ch_out))
    if relu:
        layers.append(nn.LeakyReLU(0.2, inplace=True))

    layers = nn.Sequential(*layers)

    return layers

def batched_index_select(x, idx):
    r"""fetches neighbors features from a given neighbor idx

    Args:
        x (Tensor): input feature Tensor
                :math:`\mathbf{X} \in \mathbb{R}^{B \times C \times N \times 1}`.
        idx (Tensor): edge_idx
                :math:`\mathbf{X} \in \mathbb{R}^{B \times N \times l}`.
    Returns:
        Tensor: output neighbors features
            :math:`\mathbf{X} \in \mathbb{R}^{B \times C \times N \times k}`.
    """
    batch_size, num_dims, num_vertices = x.shape[:3]
    k = idx.shape[-1]
    idx_base = torch.arange(0, batch_size, device=idx.device).view(-1, 1, 1) * num_vertices
    idx = idx + idx_base
    idx = idx.contiguous().view(-1)

    x = x.transpose(2, 1)
    feature = x.contiguous().view(batch_size * num_vertices, -1)[idx, :]
    feature = feature.view(batch_size, num_vertices, k, num_dims).permute(0, 3, 1, 2).contiguous()
    return feature

def pairwise_distance(x):
    """
    Compute pairwise distance of a point cloud.
    Args:
        x: tensor (batch_size, num_points, num_dims)
    Returns:
        pairwise distance: (batch_size, num_points, num_points)
    """
    x_inner = -2*torch.matmul(x, x.transpose(2, 1))
    x_square = torch.sum(torch.mul(x, x), dim=-1, keepdim=True)
    return x_square + x_inner + x_square.transpose(2, 1)

def dense_knn_matrix(x, k=16):
    """Get KNN based on the pairwise distance.
    Args:
        x: (batch_size, num_dims, num_points, 1)
        k: int
    Returns:
        nearest neighbors: (batch_size, num_points ,k) (batch_size, num_points, k)
    """
    with torch.no_grad():
        x = x.transpose(2, 1).squeeze(-1)
        batch_size, n_points, n_dims = x.shape
        _, nn_idx = torch.topk(-pairwise_distance(x.detach()), k=k)
        center_idx = torch.arange(0, n_points, device=x.device).repeat(batch_size, k, 1).transpose(2, 1)
    return torch.stack((nn_idx, center_idx), dim=0)

class KnnGraph(nn.Module):
    """
    Find the neighbors' indices based on dilated knn
    """
    def __init__(self, k=16):
        super(KnnGraph, self).__init__()
        self.k = k
        self.knn = dense_knn_matrix

    def forward(self, x):
        edge_index = self.knn(x, self.k)
        return edge_index

class EdgeAttn(nn.Module):
    """
    Edge attention layer
    """
    def __init__(self, in_channels, out_channels):
        super(EdgeAttn, self).__init__()
        self.edge = nn.Conv2d(in_channels*2, out_channels, 1, bias=True)
        self.att = nn.Conv2d(in_channels*2, out_channels, 1, bias=True)

    def forward(self, x, edge_index):
        x_i = batched_index_select(x, edge_index[1])
        x_j = batched_index_select(x, edge_index[0])

        x_edge = self.edge(torch.cat([x_i, x_j - x_i], dim=1))
        attn = self.att(torch.cat([x_i, x_j - x_i], dim=1))

        attn = nn.Softmax(dim=-1)(attn)

        x = x_edge * attn

        x = torch.sum(x, dim=-1, keepdim=True)

        return x

class GraphConv2d(nn.Module):
    """
    Static graph convolution layer
    """
    def __init__(self, in_channels, out_channels):
        super(GraphConv2d, self).__init__()
        self.gconv = EdgeAttn(in_channels, out_channels)

    def forward(self, x, edge_index):
        return self.gconv(x, edge_index)

class DynConv2d(GraphConv2d):
    """
    Dynamic graph convolution layer
    """
    def __init__(self, in_channels, out_channels, kernel_size=16):
        super(DynConv2d, self).__init__(in_channels, out_channels)
        self.knn_graph = KnnGraph(kernel_size)

    def forward(self, x):
        edge_index = self.knn_graph(x)
        return super(DynConv2d, self).forward(x, edge_index)

class DynGCN(nn.Module):
    def __init__(self, in_channels=81, hid_channels=96, out_channels=9, kernel_size=16):
        super(DynGCN, self).__init__()
        self.in_channels = in_channels
        self.knn = KnnGraph(kernel_size)
        self.layer1 = GraphConv2d(in_channels, hid_channels)
        self.layer2 = DynConv2d(hid_channels, hid_channels, kernel_size)
        self.layer3 = DynConv2d(hid_channels, out_channels, kernel_size)

    def forward(self, x):
        edge_index = self.knn(x[:, 0:3])
        x = self.layer1(x[:, 3:self.in_channels+3], edge_index)
        x = self.layer2(x)
        x = self.layer3(x)

        return x

class Graph_Prop(nn.Module):

    def __init__(self):
        super(Graph_Prop, self).__init__()

        self.Graph_Prop_Block = DynGCN()
        self.guide_to_graph = nn.Conv2d(in_channels=81,
                                           out_channels=81,
                                           kernel_size=(9, 9),
                                           stride=3,
                                           groups=81,
                                           padding=(3, 4),
                                           bias=False)
        self.depth_to_graph = nn.Conv2d(in_channels=1,
                                           out_channels=1,
                                           kernel_size=(9, 9),
                                           stride=3,
                                           groups=1,
                                           padding=(3, 4),
                                           bias=False)
        self.graph_to_depth = nn.ConvTranspose2d(9, 9, kernel_size = 3, stride = 3, groups=9, padding = 0, bias=False)
        self.conv_sum = nn.Conv2d(in_channels=9,
                                       out_channels=1,
                                       kernel_size=(1, 1),
                                       stride=1,
                                       padding=0,
                                       bias=False)
        x_3d, y_3d = self.camera()
        self.x_3d = x_3d
        self.y_3d = y_3d

        self._ini_conv()

    def _ini_conv(self):
        weight = torch.zeros(81, 1, 9, 9)
        total = 0
        for i in range(9):
            for j in range(9):
                weight[total, 0, i, j] = 1
                total += 1

        weight_guidance_groups = torch.zeros(81, 1, 9, 9)
        for i in range(81):
            weight_guidance_groups[i, 0] = weight[i, 0]

        self.guide_to_graph.weight = nn.Parameter(weight_guidance_groups)

        for param in self.guide_to_graph.parameters():
            param.requires_grad = False

        weight_blur = torch.zeros(1, 1, 9, 9)
        weight_blur[0, 0] = weight[40, 0]

        self.depth_to_graph.weight = nn.Parameter(weight_blur)

        for param in self.depth_to_graph.parameters():
            param.requires_grad = False

        weight2 = torch.zeros(9, 1, 3, 3)
        total = 0
        for i in range(3):
            for j in range(3):
                weight2[total, 0, i, j] = 1
                total += 1

        up_weight_guidance_groups = torch.zeros(9, 1, 3, 3)
        for i in range(9):
            up_weight_guidance_groups[i, 0] = weight2[i, 0]

        self.graph_to_depth.weight = nn.Parameter(up_weight_guidance_groups)
        for param in self.graph_to_depth.parameters():
            param.requires_grad = False

        weight_sum = torch.ones(1, 9, 1, 1)

        self.conv_sum.weight = nn.Parameter(weight_sum)
        for param in self.conv_sum.parameters():
            param.requires_grad = False

    def camera(self):
        x_axis = np.arange(0, 102, 1)
        y_axis = np.arange(0, 76, 1)
        xx, yy = np.meshgrid(x_axis, y_axis)
        xx = torch.from_numpy(xx)
        yy = torch.from_numpy(yy)
        fx_d = 582.62448167737955/2.0
        fy_d = 582.69103270988637/2.0
        cx_d = 313.04475870804731/2.0
        cy_d = 238.44389626620386/2.0

        x_3d = (xx - cx_d)/fx_d
        y_3d = (yy - cy_d)/fy_d

        return x_3d, y_3d

    def forward(self, guidance, ini_depth, sparse_depth):
        sparse_mask = sparse_depth.sign()
        ini_depth =  (1 - sparse_mask) * ini_depth + sparse_mask * sparse_depth

        ini_depth = self.depth_to_graph(ini_depth)

        x_3d = self.x_3d.to(ini_depth)
        y_3d = self.y_3d.to(ini_depth)
        x_3d = x_3d * ini_depth * 3
        y_3d = y_3d * ini_depth * 3

        loc = torch.cat([x_3d, y_3d, ini_depth], dim=1)

        x = self.guide_to_graph(guidance)
        x = torch.cat([loc, x], dim=1)
        width = x.shape[2]
        height = x.shape[3]

        x = x.view(x.shape[0], x.shape[1], width*height, 1)
        x = self.Graph_Prop_Block(x)
        x = x.view(x.shape[0], x.shape[1], width, height)

        x = self.graph_to_depth(x)
        x = self.conv_sum(x)

        x = x.narrow(3, 1, sparse_depth.shape[-1])

        return x

class GraphCSPN(nn.Module):
    def __init__(self, args):
        super(GraphCSPN, self).__init__()

        self.args = args

        # Encoder
        self.conv1_rgb = conv_bn_relu(3, 48, kernel=3, stride=1, padding=1,
                                      bn=False)
        self.conv1_dep = conv_bn_relu(1, 16, kernel=3, stride=1, padding=1,
                                      bn=False)

        net = torchvision.models.resnet34(pretrained=True)

        # 1/1
        self.conv2 = net.layer1
        # 1/2
        self.conv3 = net.layer2
        # 1/4
        self.conv4 = net.layer3
        # 1/8
        self.conv5 = net.layer4

        del net

        # 1/16
        self.conv6 = conv_bn_relu(512, 512, kernel=3, stride=2, padding=1)

        # Shared Decoder
        # 1/8
        self.dec5 = convt_bn_relu(512, 256, kernel=3, stride=2,
                                  padding=1, output_padding=1)
        # 1/4
        self.dec4 = convt_bn_relu(256+512, 128, kernel=3, stride=2,
                                  padding=1, output_padding=1)
        # 1/2
        self.dec3 = convt_bn_relu(128+256, 64, kernel=3, stride=2,
                                  padding=1, output_padding=1)

        # 1/1
        self.dec2 = convt_bn_relu(64+128, 64, kernel=3, stride=2,
                                  padding=1, output_padding=1)

        # Init Depth Branch
        # 1/1
        self.id_dec1 = conv_bn_relu(64+64, 64, kernel=3, stride=1,
                                    padding=1)
        self.id_dec0 = conv_bn_relu(64+64, 1, kernel=3, stride=1,
                                    padding=1, bn=False, relu=True)

        # Guidance Branch
        # 1/1
        self.gd_dec1 = conv_bn_relu(64+64, 64, kernel=3, stride=1,
                                    padding=1)
        self.gd_dec0 = conv_bn_relu(64+64, 81, kernel=3, stride=1,
                                    padding=1, bn=False, relu=False)

        # Graph propagation layer
        self.graph_prop = Graph_Prop()

    def _concat(self, fd, fe, dim=1):
        # Decoder feature may have additional padding
        _, _, Hd, Wd = fd.shape
        _, _, He, We = fe.shape

        # Remove additional padding
        if Hd > He:
            h = Hd - He
            fd = fd[:, :, :-h, :]

        if Wd > We:
            w = Wd - We
            fd = fd[:, :, :, :-w]

        f = torch.cat((fd, fe), dim=dim)

        return f

    def forward(self, sample):
        rgb = sample['rgb']
        dep = sample['dep']

        # Encoding
        fe1_rgb = self.conv1_rgb(rgb)
        fe1_dep = self.conv1_dep(dep)

        fe1 = torch.cat((fe1_rgb, fe1_dep), dim=1)

        fe2 = self.conv2(fe1)
        fe3 = self.conv3(fe2)
        fe4 = self.conv4(fe3)
        fe5 = self.conv5(fe4)
        fe6 = self.conv6(fe5)

        # Shared Decoding
        fd5 = self.dec5(fe6)
        fd4 = self.dec4(self._concat(fd5, fe5))
        fd3 = self.dec3(self._concat(fd4, fe4))
        fd2 = self.dec2(self._concat(fd3, fe3))

        # Init Depth Decoding
        id_fd1 = self.id_dec1(self._concat(fd2, fe2))
        pred_init = self.id_dec0(self._concat(id_fd1, fe1))

        # Guidance Decoding
        gd_fd1 = self.gd_dec1(self._concat(fd2, fe2))
        guide = self.gd_dec0(self._concat(gd_fd1, fe1))

        # Diffusion
        y = self.graph_prop(guide, pred_init, dep)

        # Remove negative depth
        output = torch.clamp(y, min=0)

        return output


import copy
import math

import torch
from trains import Task
from ignite.engine import Engine
from torchvision.transforms import functional as F

from torchvision_references import utils

def create_trainer(model, device):
    def update_model(engine, batch):
        # images, targets = copy.deepcopy(batch)
        images, targets = prepare_batch(copy.deepcopy(batch), device=torch.device('cpu'))
        images_model, targets_model = prepare_batch(batch, device=device)

        loss_dict = model(images_model, targets_model)
        losses = sum(loss for loss in loss_dict.values())

        # reduce losses over all GPUs for logging purposes
        loss_dict_reduced = utils.reduce_dict(loss_dict)
        losses_reduced = sum(loss for loss in loss_dict_reduced.values())

        loss_value = losses_reduced.item()

        engine.state.optimizer.zero_grad()
        if not math.isfinite(loss_value):
            print("Loss is {}, resetting loss and skipping training iteration".format(loss_value))
            print('Loss values were: ', loss_dict_reduced)
            print('Input labels were: ', [target['labels'] for target in targets])
            print('Input boxes were: ', [target['boxes'] for target in targets])
            loss_dict_reduced = {k: torch.tensor(0) for k, v in loss_dict_reduced.items()}
        else:
            losses.backward()
            engine.state.optimizer.step()

        if engine.state.warmup_scheduler is not None:
            engine.state.warmup_scheduler.step()

        if type(engine.state.scheduler) == torch.optim.lr_scheduler.CyclicLR:
            engine.state.scheduler.step()

        images_model = targets_model = None
        loss_dict_reduced_detach = {key: val.cpu() for key, val in loss_dict_reduced.items()}

        return images, targets, loss_dict_reduced_detach
    return Engine(update_model)

def create_evaluator(model, device):
    def update_model(engine, batch):
        images, targets = prepare_batch(batch, device=device)
        images_model = copy.deepcopy(images)

        torch.cuda.synchronize()
        with torch.no_grad():
            outputs = model(images_model)

        outputs = [{k: v.to(device) for k, v in t.items()} for t in outputs]

        res = {target["image_id"].item(): output if len(output['boxes']) > 0 else {}
               for target, output in zip(targets, outputs)}
        engine.state.coco_evaluator.update(res)

        # Frame level statistics
        target_labels = [target['labels'].cpu() for target in targets]

        pred_labels = []
        iter_corrects = []
        for test_score_thr in engine.state.test_score_thr:
            pred_labels.append([val['labels'][val['scores'] > test_score_thr].cpu()
                           if len(val) > 0 else torch.tensor([], dtype=torch.int64) for key, val in res.items()])
            iter_corrects.append([torch.all(torch.eq(target.unique(), pred.unique())).item()
                             if target.unique().size() == pred.unique().size() else False
                             for target, pred in zip(target_labels, pred_labels[-1])])

        images_model = outputs = None

        return images, targets, res, pred_labels, iter_corrects
    return Engine(update_model)

def create_annotator(model, device):
    def update_model(engine, batch):
        images, frames = batch
        images = list(F.to_tensor(F.to_pil_image(image)).to(device, non_blocking=True) for image in images)
        images_model = copy.deepcopy(images)

        torch.cuda.synchronize()
        with torch.no_grad():
            outputs = model(images_model)

        outputs = [{k: v.to(device) for k, v in t.items()} for t in outputs]

        res = {frame.id: output if len(output['boxes']) > 0 else {}
               for frame, output in zip(frames, outputs)}

        for image, frame, result in zip(images, frames, res.items()):
            if len(result[1]) == 0:
                frame.add_annotation(frame_class=[engine.state.frame_labels['negative']], confidence=1.0)
            else:
                confidence_list = []
                # Apply score threshold
                task_params = Task.current_task().data.execution.parameters
                for score, label_id, box in zip(result[1]['scores'], result[1]['labels'], result[1]['boxes']):
                    if float(score.cpu()) > float(task_params['test_score_thr']):
                        label_id = int(label_id.cpu())
                        label = engine.state.label_enum[label_id]
                        d1, d2, d3 = image.shape
                        resize_box_factors = [frame.width / d3, frame.height / d2] * 2
                        resized_box = box.cpu() * torch.tensor(resize_box_factors)
                        box_xywh = resized_box[:2].tolist() + (resized_box[2:] - resized_box[:2]).tolist()
                        confidence = float(score.cpu())
                        confidence_list.append(confidence)
                        frame.add_annotation(labels=[label], box2d_xywh=box_xywh, confidence=confidence)

                if len(confidence_list) == 0:
                    confidence_list = [score for score in result[1]['scores']]
                    frame.add_annotation(frame_class=[engine.state.frame_labels['negative']],
                                         confidence=1 - max(confidence_list))
                else:
                    frame.add_annotation(frame_class=[engine.state.frame_labels['positive']],
                                         confidence=max(confidence_list))

        return images, res, frames
    return Engine(update_model)

def prepare_batch(batch, device=None):
    images, targets = batch
    images = list(F.to_tensor(F.to_pil_image(image)).to(device, non_blocking=True) for image in images)
    targets = [{k: torch.as_tensor(v).to(device, non_blocking=True) for k, v in t.items()} for t in targets]
    # images = list(image.to(device, non_blocking=True) for image in images)
    # targets = [{k: v.to(device, non_blocking=True) for k, v in t.items()} for t in targets]
    return images, targets


from sklearn.feature_extraction.text import CountVectorizer

def prepare_vocabulary(data, langs):
    features = set()

    for lang in langs:
        corpus = data[data.lang == lang]['text']
        vectorizer = CountVectorizer(analyzer='char',
                                     max_features=200,
                                     ngram_range=(3, 3))
        _ = vectorizer.fit_transform(corpus)
        f = vectorizer.get_feature_names_out()
        features.update(f)

    vocabulary = dict()
    for i, f in enumerate(features):
        vocabulary[f] = i

    return vocabulary


from __future__ import annotations

import json
import subprocess

BWENV_PREFIX = 'bwenv://'

def is_bw_unlocked(session: str) -> bool:
    bw_res = subprocess.run(
        ['bw', 'status', '--session', f'{session}'],
        capture_output=True,
    )
    bw_status = json.loads(bw_res.stdout)
    return bw_status['status'] == 'unlocked'


from bs4 import BeautifulSoup
import sys
from pprint import pprint
import json
import re

def clean(s):
    return re.sub("\s{2,}", " ", s.replace("\t", " "))

def parse(filename, part):

    with open(filename) as f:
        html = f.read()

    rows = BeautifulSoup(html, 'html.parser').find_all("tr")

    table = [[td.text for td in row.find_all("td")] for row in rows]

    # print(table)

    table_dict = {
        "part": part,
        "title": {
            "chinese": table[1][1],
            "legge": table[1][2],
            "susuki": table[1][3],
            "goddard": table[1][4],
        },
        "verses": []
    }

    for verse in table[2:]:
        # pprint(verse)
        table_dict["verses"].append({
            "verse": verse[0],
            "chinese": clean(verse[1]),
            "legge": clean(verse[2]),
            "susuki": clean(verse[3]),
            "goddard": clean(verse[4]),
        })

    # result = json.dumps(table_dict, ensure_ascii=False)

    # with open(filename + ".json", "w") as writefile:
    #     writefile.write(result)

    return table_dict

if __name__ == "__main__":
    filenames = sys.argv[1:]

    parsed = [parse(f, i + 1) for (i, f) in enumerate(filenames)]

    print(json.dumps(parsed, ensure_ascii=False))


import json
import logging

from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.http import HttpRequest
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods

from helpers.date_helper import pretty_date
from email_api.helpers import auth_helper
from email_api.helpers import request_helper
from accounts.models import User

from .models import CustomTalent
from .models.ray import Ray
from .models.message_thread import MessageThread
from .models.direct_message import DirectMessage

DEFAULT_RAY_MESSAGES = settings.DEFAULT_RAY_MESSAGES
RAY_SOURCES = settings.RAY_SOURCES
LOGIN_PARAMS = ('/auth', None)

@require_http_methods(["GET"])
@login_required(login_url=LOGIN_PARAMS[0], redirect_field_name=LOGIN_PARAMS[1])
def user_rays_direct(request: HttpRequest) -> JsonResponse:
    """
    List Holons users' Direct Rays on GET

    * take direct rays
    * take fixed rays and load messages in each of them

    :return: JsonResponse
    """

    rays_data = list_user_direct_rays(request.user)

    return JsonResponse({
        'result': 'OK',
        'rays': rays_data,
    })

def list_user_direct_rays(user: User) -> list:
    """
    Prepare a list of Ray->MessageThread->DirectMessage
    Enrich with from: and to: usernames

    """

    add_default_rays(user)
    user_direct_rays = []
    for ray in user.rays_direct.all():
      ray_message_threads = []
      messages_count = 0
      thread_count = 0
      user_from = ''
      user_to = ''
      for message_thread in ray.message_thread.all():
        messages = []
        note_to_self = False
        if not message_thread.is_archived and not message_thread.is_deleted:
          thread_count += 1
        if message_thread.messages:
          user_from = message_thread.messages.first().user_from.first()

        for message in message_thread.messages.all():
          sender_handle = ''
          sender_userpic = ''
          reciever_handle = ''
          reciever_userpic = ''
          if message.user_from.first():
            sender_handle = message.user_from.first().handle
            sender_account_status = message.user_from.first().account_status
            if sender_account_status == '':
              sender_account_status = settings.ACCOUNT_STATUSES[0][0]
            try:
              sender_userpic = message.user_from.first().userpic.url
            except ValueError:
              logging.info('Userpic does not exist')
          if message.user_to.first():
            reciever_handle = message.user_to.first().handle
            reciever_account_status = message.user_to.first().account_status
            if reciever_account_status == '':
              reciever_account_status = settings.ACCOUNT_STATUSES[0][0]
            try:
              reciever_userpic = message.user_to.first().userpic.url
            except ValueError:
              logging.info('Userpic does not exist')
          if message.user_to == message.user_from:
            note_to_self = True
          messages.extend([{
            'id': message.id,
            'subject': message.subject,
            'body': message.body,
            'pub_date': message.pub_date,
            'pub_date_pretty': pretty_date(message.pub_date),
            'is_archived': message.is_archived,
            'is_deleted': message.is_deleted,
            'provider': message.provider,
            'user_to': reciever_handle,
            'user_to_userpic': reciever_userpic,
            'user_to_account_status': reciever_account_status,
            'user_from': sender_handle,
            'user_from_userpic': sender_userpic,
            'user_from_account_status': sender_account_status,
          }])
        ray_message_threads.extend([{
          'id': message_thread.id,
          'is_archived': message.message_thread.is_archived,
          'is_deleted': message.message_thread.is_deleted,
          'messages': messages
        }])

      # :todo: investigate a correct way to get user_to
      user_to = ray.users.all().first()
      check_users = ray.users.all().exclude(pk=user_from.id)
      for el in check_users:
        user_to = el

      short_name = user_to.username.split(' ')[0] + ' @' + user_to.handle
      if note_to_self:
        short_name = 'Notes to self'

      userpic = ''
      if user_to.userpic:
        userpic = user_to.userpic.url
      user_direct_rays.extend([{
        'id': ray.id,
        'short_name': short_name,
        'user_from_id': str(user_from.id),
        'user_to_id': str(user_to.id),
        'user_from': user_from.username,
        'user_to': user_to.username,
        'message_threads': ray_message_threads,
        'messages_count': thread_count,
        'userpic': userpic,
      }])

    return user_direct_rays

@require_http_methods(["POST"])
@login_required(login_url=LOGIN_PARAMS[0], redirect_field_name=LOGIN_PARAMS[1])
def rays_custom_message(request: HttpRequest) -> JsonResponse:
    """
    Send a custom message to the fixed ray
    * Moneta
    * Talant
    * etc

    :param request:
    :return:

    """

    if {} == json.loads(request.body):
      return JsonResponse({
          'result': 'error',
          'message': 'empty request'
      }, status=400)

    data = json.loads(request.body)
    title = data.get('title', None)
    description = data.get('description', None)
    ray_source_id = data.get('ray_source', None)
    # :todo: maybe we could unify errors?
    if not title:
      return JsonResponse({'result': 'error', 'message': 'empty title'}, status=400)
    if not description:
      return JsonResponse({'result': 'error', 'message': 'empty description'}, status=400)
    if not ray_source_id:
      return JsonResponse({'result': 'error', 'message': 'empty ray source'}, status=400)
    ray_source = RAY_SOURCES.index(ray_source_id)
    message = CustomTalent.objects.create(
        ray_source=ray_source,
        title=title,
        description=description
    )
    message.save()
    return JsonResponse({
        'result': 'OK',
        'message': message.title
    })

@require_http_methods(["POST"])
@login_required(login_url=LOGIN_PARAMS[0], redirect_field_name=LOGIN_PARAMS[1])
def rays_send_direct_message(request: HttpRequest) -> JsonResponse:
    """Send direct messages between two users via common Ray

    * get a common Ray for user_from and user_to
    * create new ray message in the common Ray

    """

    if {} == json.loads(request.body):
      return JsonResponse({
          'result': 'error',
          'message': 'empty request'
      }, status=400)

    data = json.loads(request.body)
    user_from = request.user
    to = data.get('to', None)

    if data.get('reply_to_id', False):
      user_to = get_message_author(data.get('reply_to_id'))
      message_sent = rays_add_thread_message(
        message_body=data.get('message_body', ''),
        reply_to_id=data.get('reply_to_id', None)
      )
      user_to.rays_dm_recieved.add(message_sent)
      user_from.rays_dm_sent.add(message_sent)
      return JsonResponse({
          'result': 'OK',
          'message': 'message sent as a reply',
      })
    elif not to:
      return JsonResponse({
                          'result': 'error',
                          'message': 'empty to'}, status=400)
    if not data.get('subject', None):
      return JsonResponse({
                          'result': 'error',
                          'message': 'empty subject'}, status=400)
    if not data.get('message_body', None):
      return JsonResponse({
                          'result': 'error',
                          'message': 'empty message_body'}, status=400)
    user_to = User.objects.filter(pk=to).get()
    subject = data.get('subject', '')
    message_body = data.get('message_body', '')
    direct_message = create_direct_message(user_from, user_to, subject, message_body)
    return JsonResponse({
        'result': 'OK',
        'message': 'message sent',
        'message_id': direct_message.id,
    })

def create_direct_message(user_from, user_to, subject, message_body) -> DirectMessage:
    """Prepare and save DirectMessage to the DB"""
    ''' Direct Ray '''
    common_ray = get_common_ray(user_from, user_to)
    ''' MessageThread '''
    message_thread = MessageThread.objects.create()
    common_ray.message_thread.add(message_thread)
    ''' DirectMessage '''
    direct_message = DirectMessage.objects.create(
      subject=subject,
      body=message_body,
      message_thread=message_thread,
    )
    user_from.rays_dm_sent.add(direct_message)
    user_to.rays_dm_recieved.add(direct_message)
    return direct_message

def get_common_ray(user_from: User, user_to: User) -> Ray:
    """Get common Ray between two users given

    * search for a Ray shared by the two users
    * create one if users have no Rays in common

    :param user_from:
    :param user_to:
    :returns: Common Ray ready for messaging
    """

    ''' In case the user has no direct rays at all '''
    if len(user_from.rays_direct.all()) == 0:
        common_ray = create_common_ray(user_from, user_to)
        return common_ray
    else:
      ''' Search for a common Ray '''
      for ray in user_from.rays_direct.all():
        for ray_user in ray.users.all():
          if user_to == ray_user:
            return ray
    common_ray = create_common_ray(user_from, user_to)
    return common_ray

def create_common_ray(user_from: User, user_to: User) -> Ray:
      """Create a common Ray for direct messaging between two users

      * create ray
      * attach users

      :param user_from:
      :param user_to:
      :returns: Common Ray ready for messaging
      """

      common_ray = Ray.objects.create()
      user_from.rays_direct.add(common_ray)
      user_to.rays_direct.add(common_ray)
      return common_ray

@require_http_methods(["DELETE"])
@login_required(login_url=LOGIN_PARAMS[0], redirect_field_name=LOGIN_PARAMS[1])
def rays_thread_delete(request: HttpRequest, thread_id: int) -> JsonResponse:
    message_thread = MessageThread.objects.filter(pk=thread_id).get()
    message_thread.is_deleted = True
    message_thread.save()
    return JsonResponse({
        'result': 'OK',
    })

@require_http_methods(["PATCH"])
@login_required(login_url=LOGIN_PARAMS[0], redirect_field_name=LOGIN_PARAMS[1])
def rays_thread_archive(request: HttpRequest, thread_id: int) -> JsonResponse:
    message_thread = MessageThread.objects.filter(pk=thread_id).get()
    message_thread.is_archived = True
    message_thread.save()
    return JsonResponse({
        'result': 'OK',
    })

def rays_add_thread_message(message_body: str, reply_to_id: int) -> DirectMessage:
    ''' DirectMessage '''
    parent_message = DirectMessage.objects.filter(pk=reply_to_id).get()
    message_thread = parent_message.message_thread
    direct_message = DirectMessage.objects.create(
      subject=parent_message.subject,
      body=message_body,
      message_thread=message_thread
    )
    return direct_message

def add_default_rays(user: User) -> bool:
    """Enhance user's direct_rays with default messages from RAY_SOURCES

    * get all user's DirectMessages
    * see if the result lacks message from any of the RAY_SOURCES
    * send message RAYSOURCE.name -> user.id

    :returns: True if any message sent, False if all default messages are there
    """

    fixed_users = get_fixed_users()
    fixed_users.insert(0, user.id)
    for f_id in fixed_users:
      rays_opened = user.rays_dm_recieved.all().filter(user_from__id=f_id).count()
      if 0 == rays_opened:
        if f_id == user.id:
          f_user = user
          subject = 'Note to self'
          message = 'Write something'
        else:
          f_user = User.objects.filter(pk__exact=f_id).get()
          subject = 'Greetings from: ' + f_user.username
          default_message = DEFAULT_RAY_MESSAGES[f_user.handle]
          message = default_message

        create_direct_message(
            user_from=f_user,
            user_to=user,
            subject=subject,
            message_body=message
          )

def get_fixed_users() -> list:
    """Get list of User ids based on a list of handles from SETTINGS"""

    fixed_users = User.objects.filter(handle__in=RAY_SOURCES)\
                  .values_list('id', flat=True)
    return list(fixed_users)

def get_message_author(message_id: int) -> User:
    message = DirectMessage.objects.filter(pk=message_id).get()
    return message.user_from.first()


import json

from utils import log

def save(data, path):
    """
    Saving data in specific path
    """
    s = json.dumps(data, indent=2, ensure_ascii=False)
    with open(path, mode='w', encoding='utf-8') as f:
        log('saving', path, s, data)
        f.write(s)

def load(path):
    """
    Load data from specific path
    """
    with open(path, encoding='utf-8') as f:
        s = f.read()
        log('loading', s)
        return json.loads(s)

class Model:
    @classmethod
    def db_path(cls):
        class_name = cls.__name__
        path = 'db/{}.txt'.format(class_name)
        return path

    @classmethod
    def new(cls, form):
        m = cls(form)
        return m

    @classmethod
    def all(cls):
        path = cls.db_path()
        models = load(path)
        ms = [cls.new(m) for m in models]
        return ms

    @classmethod
    def find_by(cls, **kwargs):
        """
        find an object by specified attr
        """
        ms = cls.all()
        keys = kwargs.keys()
        log('ms', ms)
        log('keys', kwargs)
        for m in ms:
            d = m.__dict__
            try:
                if all(d[k] == kwargs[k] for k in keys):
                    return m
            except KeyError as e:
                log('Method {}.find_by called but raise an error: '.format(cls.__name__), e)
                return None
        return None

    @classmethod
    def find(cls, id):
        return cls.find_by(id=id)

    @classmethod
    def find_all(cls, **kwargs):
        """
        find all objects by specified attr
        """
        wanted = []
        ms = cls.all()
        keys = kwargs.keys()
        for m in ms:
            d = m.__dict__
            try:
                if all(d[k] == kwargs[k] for k in keys):
                    wanted.append(m)
            except KeyError as e:
                log('Method {}.find_by called but raise an error: '.format(cls.__name__), e)
                return []
        return wanted

    def save(self):
        ms = self.all()
        if (not hasattr(self, 'id')) or self.id is None:
            if len(ms) > 0:
                self.id = ms[-1].id + 1
            else:
                self.id = 0
            ms.append(self)
        else:
            # search
            index = -1
            for i, m in enumerate(ms):
                if m.id == self.id:
                    index = i
                    break
            if index > -1:
                ms[index] = self

        log('models', ms)
        log('self.id', self.id)

        data = [m.__dict__ for m in ms]
        path = self.db_path()
        save(data, path)

    def remove(self):
        ms = self.all()
        if self.id is not None:
            # search
            index = -1
            for i, m in enumerate(ms):
                if m.id == self.id:
                    index = i
                    break
            if index > -1:
                del ms[index]
                data = [m.__dict__ for m in ms]
                path = self.db_path()
                save(data, path)

    def __repr__(self):
        class_name = self.__class__.__name__
        data = '\n'.join('{}: {}'.format(k, v) for k, v in self.__dict__.items())
        return '< {}\n{} >\n'.format(class_name, data)


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

from math import pi, cos, sin, radians, sqrt

import matplotlib.pyplot as pyplot
import numpy

from gridgen_no_taper import gridgen_no_taper

def rotate_values(x, y, angle):
    """Rotation by angle, +angle == counterclockwise rotation"""
    xr = x * cos(radians(angle)) - y * sin(radians(angle))
    yr = x * sin(radians(angle)) + y * cos(radians(angle))
    return xr, yr

def generate_lattice_2(nx, ny, sq_inc):
    xc = numpy.zeros((nx, ny))
    yc = numpy.zeros((nx, ny))
    for iy in range(ny):
        for ix in range(nx):
            xc[iy, ix] = (ix + 0.5 * iy) * sq_inc
            yc[iy, ix] = (iy * sq_inc * sqrt(3.0) / 2.0)
    xc -= xc[ny/2, nx/2]
    yc -= yc[ny/2, nx/2]
    return xc.flatten(), yc.flatten()

def select_antennas(x, y, angle=18.0):
    num_samples = 100
    inc_x = ((2.0 * pi) / 5.0) * num_samples
    theta = numpy.arctan2(y, x) + 2.0 * pi - radians(angle)
    theta_x = theta * num_samples
    theta_m5 = (theta_x % inc_x) / num_samples
    rad_p = r_max * (cos(pi / 5.0) / numpy.cos(theta_m5 - pi / 5.0))
    rc = (x**2 + y**2)**0.5
    sgood = rc / rad_p
    sind = numpy.argsort(sgood)
    sgood = sgood[sind]
    n_ok = numpy.argmax(sgood > 1.0) - 1
    rc_1 = rc[sind]
    x1 = x[sind]
    y1 = y[sind]
    x2 = x1[:n_ok]
    y2 = y1[:n_ok]

    xe = rad_p * numpy.cos(theta + radians(angle))
    ye = rad_p * numpy.sin(theta + radians(angle))
    return x2, y2, xe, ye

if __name__ == '__main__':
    sub_station_radius_m = 7.0
    r_max = sub_station_radius_m
    sqinc = 2.0 * r_max / 9.33
    side = 2.0 * r_max * cos(radians(54.0))
    rmin = r_max * sin(radians(54.0))
    psep = 2.0 * rmin + 0.5
    ssep = 2.0 * (r_max + side * cos(radians(18.0))) + 1.5

    # Generate the lattice
    # x, y = generate_lattice_2(30, 30, sqinc/2.0)
    x, y, _ = gridgen_no_taper(90, 20, 1.5, 10000)

    xs, ys, xe, ye = select_antennas(x, y, angle=-90.0)

    fig1 = pyplot.figure(figsize=(16, 8))
    ax1 = fig1.add_subplot(111, aspect='equal')
    ax1.plot(xe, ye, 'bs', ms=1.0)
    ax1.plot(x, y, '+')
    ax1.plot(xs, ys, 'o')
    ax1.grid()
    pyplot.show()


import typing

APIGEE_BASE_URL = "https://api.enterprise.apigee.com/v1/"
APIGEE_DAPI_URL = "https://apigee.com/dapi/api/"

APIGEE_ORG_TO_ENV = {
    "nhsd-nonprod": [
        "internal-dev",
        "internal-dev-sandbox",
        "internal-qa",
        "internal-qa-sandbox",
        "ref",
    ],
    "nhsd-prod": ["dev", "int", "sandbox", "prod"],
}

def portal_uri(org: typing.Literal["nhsd-nonprod", "nhsd-prod"]) -> str:
    portal_ids = {
        "nhsd-nonprod": "nhsd-nonprod-developerportal",
        "nhsd-prod": "nhsd-prod-developerportal",
    }
    if org not in portal_ids:
        raise ValueError(f"Invalid organization name {org}")
    return f"https://apigee.com/portals/api/sites/{portal_ids[org]}/apidocs"


from tkinter import *
root=Tk()
root.title("My Weather App")
root.geometry("350x300")
root.overrideredirect(True)

import requests
import json

#Setting labels
city_name_label=Label(root, text="City Name",font=("Helvetica", 18,'bold'),bg="white")
city_name_label.place(relx=0.6,rely=0.15,anchor=CENTER)

city_entry = Entry(root , fg = "Black")
city_entry.place(relx=0.6,rely=0.35,anchor=CENTER)

weather_info_label = Label(root,text="Weather: ", bg="white", font=("bold", 18))
weather_info_label.place(relx=0.3,rely=0.6,anchor=CENTER)

humidity_info_label = Label(root,text="Humidity: ", bg="white", font=( "bold",18))
humidity_info_label.place(relx=0.3,rely=0.7,anchor=CENTER)

def cityname():
    city_name = "    "
    api_data = requests.get("https://api.openweathermap.org/data/2.5/weather?q=" + city_name + "&appid=21cab08deb7b27f4c2b55f3e2df28ea4")
    json_data = json.loads(api_data.content)

    weather_info = json_data["weather"][0]["main"]
    print(weather_info)
    weather_info_label["text"] ="Weather : " +  str(weather_info)

    humidity_info = json_data["main"]["humidity"]
    print(humidity_info)
    humidity_info_label["text"] = "Humidity : " + str(humidity_info) + "%"

    city_entry.destroy()
    button.destroy()

root.configure(background="white")

button = Button(root , command = cityname , text = "Get Weather" , font = ("bold",18))
button.place(relx = 0.5 , rely = 0.85)
root.mainloop()


import io
import json
import os
import pkgutil
import shutil
import tarfile

import click
import requests

from .. import api, constants

def _check_if_already_exists(directory: str, force: bool):
    if os.path.exists(directory):
        if force:
            print(f"delete {directory}")
            shutil.rmtree(directory)
        else:
            print(f"{directory}: already exists (use --force to override)")
            raise click.Abort()

def _read_demo_file(file_name: str):
    resource = "/".join(["..", "demo-project", file_name])
    return pkgutil.get_data(__package__, resource)

def _setup_demo(directory: str, filter: list = None):
    files = json.loads(_read_demo_file("files.json").decode("utf-8"))
    for file in files:
        if filter and file not in filter:
            continue

        print(f"use {file}")

        content = _read_demo_file(file)

        path = os.path.join(directory, file)
        with open(path, "wb") as fd:
            fd.write(content)

def _setup_submission(directory: str, code_tar: io.BytesIO):
    tar = tarfile.open(fileobj=code_tar)
    for member in tar.getmembers():
        path = os.path.join(directory, member.name)
        print(f"extract {path}")

        os.makedirs(os.path.dirname(path), exist_ok=True)

        fileobj = tar.extractfile(member)
        with open(path, "wb") as fd:
            fd.write(fileobj.read())

def setup(
    session: requests.Session,
    clone_token: str,
    submission_number: str,
    project_name: str,
    directory: str,
    model_directory: str,
    force: bool,
    no_model: bool,
):
    _check_if_already_exists(directory, force)

    push_token = session.post(f"/v1/projects/{project_name}/tokens", json={
        "type": "PERMANENT",
        "cloneToken": clone_token
    }).json()

    dot_crunchdao_path = os.path.join(directory, constants.DOT_CRUNCHDAO_DIRECTORY)
    os.makedirs(dot_crunchdao_path)

    project_file_path = os.path.join(dot_crunchdao_path, constants.PROJECT_FILE)
    with open(project_file_path, "w") as fd:
        fd.write(project_name)

    token_file_path = os.path.join(dot_crunchdao_path, constants.TOKEN_FILE)
    with open(token_file_path, "w") as fd:
        fd.write(push_token['plain'])

    try:
        code_tar = io.BytesIO(
            session.get(f"/v1/projects/{project_name}/clone", params={
                "pushToken": push_token['plain'],
                "submissionNumber": submission_number,
                "includeModel": not no_model,
            }).content
        )
    except api.NeverSubmittedException:
        _setup_demo(directory)
    else:
        _setup_submission(directory, code_tar)

    path = os.path.join(directory, model_directory)
    os.makedirs(path, exist_ok=True)


from tensorflow.keras.preprocessing import image
from kantan_ml import KantanML, KantanModel
import argparse
import glob, os

if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('mode', choices=['train', 'inference'], help='mode: train or inference')
    parser.add_argument('--model', type=str, default='mini_cnn', help='enter your model (mini_dnn, mini_cnn, efficientnetb0)')
    parser.add_argument('--image-num', type=int, default=5, help='enter image number')
    parser.add_argument('--labels', type=str, nargs='+', help='enter your labels')
    parser.add_argument('--save-dir', type=str, help='enter your select directory')
    parser.add_argument('--images-url', type=str, nargs='+', help='enter your inference image')

    args = parser.parse_args()

    if args.mode == 'train':
        ml = KantanML('http://localhost:4444/wd/hub',
                      output_dir=os.path.dirname(args.save_dir),
                      output_file=os.path.basename(args.save_dir))
        ml.download_images(args.labels, args.image_num)
        model = ml.get_model(args.model)
        model = ml.train(model)

    if args.mode == 'inference':
        labels = args.labels
        model_path = sorted(glob.glob(os.path.join(args.save_dir, 'models/*.h5')))[-1]
        kmodel = KantanModel(model_path, labels)
        for img_url in args.images_url:
            outputs = kmodel.inference_for_url_probability(img_url)
            outputs = sorted([ [k, outputs[k]]for k in outputs.keys() ], key=lambda x:x[1], reverse=True)
            print()
            print(' - - - - inference image - - - - ')
            print('URL:', img_url)
            print('INFERRED LABELS:')
            for i, (k, p) in enumerate(outputs):
                print(f' [ {i+1} ] { k }: { round(p*100, 2) }%')
            print(' - - - - - - - - - - - - - - - - ')


#!usr/bin/env python
__author__ = "Xiaoguang Zhang"
__email__ = "xzhang@westwoodrobotics.net"
__copyright__ = "Copyright 2020 Westwood Robotics"
__date__ = "Jan 8, 2020"
__version__ = "0.0.1"
__status__ = "Prototype"

# Encoder and functions

import time
import os
import numpy
import spidev
from MPS_CONTROL_TABLE import *

# Enable SPI
spi = spidev.SpiDev()

class MPS_Encoder(object):

    def __init__(self, name, chip_bus, cs, max_speed, mode):
        self.name = name
        self.chip_bus = chip_bus
        self.cs = cs
        self.max_speed = max_speed
        self.mode = mode

    def connect(self):
        # Open a connection to a specific bus and device (chip select pin)
        spi.open(self.chip_bus, self.cs)
        # Set SPI speed and mode
        spi.max_speed_hz = self.max_speed
        spi.mode = self.mode
        print("Device connected.")

    def read_angle(self):
        # Read angle from device
        data = spi.readbytes(2)
        high_byte = data[0] << 8
        low_byte = data[1]
        angle = (high_byte + low_byte) >> 4  # Get rid of last 4 bit whatever
        return angle

    def read_BCT(self):
        # Read the BCT register value
        send = 0b01000010
        spi.writebytes([send, 0])
        data = spi.readbytes(2)
        BTC = data[0]
        return BTC

    def write_BTC(self, BTC):
        # Write the BCT register value
        # BTC value
        send = 0b10000010
        spi.writebytes([send, BTC])
        time.sleep(0.02)
        data = spi.readbytes(2)
        high_byte = data[0]
        if high_byte == BTC:
            return True
        else:
            return False

    def release(self):
        # Disconnect the device
        spi.close()
        print("Device released.")

    def read_reg(self, reg_name):
        # Read from a register
        packet = INSTRUCTION.read + REG_DIC[reg_name]
        spi.writebytes([packet, 0])
        data = spi.readbytes(2)
        reg_val = data[0]
        return reg_val

    def write_reg(self, reg_name, reg_val):
        # Write to a register
        packet = INSTRUCTION.write + REG_DIC[reg_name]
        spi.writebytes([packet, reg_val])
        time.sleep(0.02)
        data = spi.readbytes(2)
        return_val = data[0]
        if return_val == reg_val:
            return True
        else:
            return False

    def home(self):
        # Zero the sensor with current position
        # Read current position:
        data = spi.readbytes(2)
        high_byte = data[0] << 8
        low_byte = data[1]
        angle = high_byte + low_byte
        a0 = 65536 - angle
        a0_h = a0 >> 8
        a0_l = a0 & 0b0000000011111111

        check = [0,0]
        packet = INSTRUCTION.write + REG_DIC['zero_high']
        spi.writebytes([packet, a0_h])
        time.sleep(0.02)
        data = spi.readbytes(2)
        return_val = data[0]
        if return_val == a0_h:
            check[0] = 1
        else:
            print("High byte update failed.")

        packet = INSTRUCTION.write + REG_DIC['zero_low']
        spi.writebytes([packet, a0_l])
        time.sleep(0.02)
        data = spi.readbytes(2)
        return_val = data[0]
        if return_val == a0_l:
            check[1] = 1
        else:
            print("Low byte update failed.")

        if check[1] and check[0]:
            print("Homed.")
            return True
        else:
            return False