import server.tcp_io as tcp_io
import server.handler as handler

import game.players as players
import game.maps as maps

import time
import threading

class GameLogic(object):
  def __init__(self, server):
    self.lock = threading.RLock()
    self.max_player_count = 2
    self.server = server
    self.map = maps.Map(25, 15)
    self.players = {}

  def event_join(self, connection_id, data):
    if len(self.players) == self.max_player_count:
      self.server.send_message(connection_id, {"type": "rejected", "data": "GAME_FULL"})
      return
    color = str(len(self.players) + 1)
    player = players.Player(color)
    self.players[connection_id] = player
    with self.lock:
      data = self.map.on_join(player)
      msg = {"type": "update", "data": data}
    self.server.send_message(connection_id, msg)

  def event_leave(self, connection_id):
    with self.lock:
      if connection_id in self.players:
        del self.players[connection_id]

  def event_move(self, connection_id, data):
    with self.lock:
      if connection_id in self.players:
        self.players[connection_id].move_command = data["command"]
        #self.map.on_move(self.players[connection_id], data["command"])

  def event_bomb(self, connection_id, data):
    with self.lock:
      if connection_id in self.players:
        player = self.players[connection_id]
        player.bomb_command = True
        #if player.can_drop_bomb():
        #  self.map.on_drop_bomb(player)
        #  player.on_drop_bomb()

  def update(self):
    with self.lock:
      self.map.update()
      for k in self.players:
        self.players[k].update()
      msg = {"type": "update", "data": self.map.pack_objects()}
      for connection_id in self.players:
        self.server.send_message(connection_id, msg)

class GameServer(object):
  FPS = 20.0
  def __init__(self):
    self.server = tcp_io.Server(('localhost', 11111))
    self.event_handler = handler.JSON_EventMap(self.server.dispatch_message)
    self.server.set_handler(self.event_handler)
    self.main_loop_function = None

  def _main_loop(self):
    while True:
      time.sleep(1.0 / self.FPS)
      self.main_loop_function()

  def bind_event(self, event_name, event_function):
    self.event_handler.bind_event(event_name, event_function)

  def bind_connection_closed(self, on_connection_closed_function):
    self.event_handler.set_on_connection_closed(on_connection_closed_function)

  def bind_main_loop(self, main_loop_function):
    self.main_loop_function = main_loop_function

  def send_message(self, connection_id, message):
    self.event_handler.send_message(connection_id, message)

  def start(self):
    threads = []
    threads.append(self.event_handler.start_thread())
    threads.append(self.server.start_thread())
    main_thread = threading.Thread(target=self._main_loop)
    main_thread.daemon = True
    main_thread.start()
    threads.append(main_thread)
    return threads

class GameMain(object):
  def __init__(self):
    self.game_server = GameServer()
    self.game_logic = GameLogic(self.game_server)

  def bind_events(self):
    self.game_server.bind_main_loop(self.game_logic.update)
    self.game_server.event_handler.set_on_connection_closed(self.game_logic.event_leave)
    self.game_server.event_handler.bind_event("join", self.game_logic.event_join)
    self.game_server.event_handler.bind_event("move", self.game_logic.event_move)
    self.game_server.event_handler.bind_event("bomb", self.game_logic.event_bomb)

  def start(self):
    """ Entry point to the game. Binds game logic events to game server events and starts the server.
        Returns the list of threads started.
    """
    self.bind_events()
    return self.game_server.start()


import os, sys

if len(sys.argv) < 2:
    print("Please provide a term to filter for.\nUsage: python searchQuery\nExiting...")
    sys.exit()

searchTerm = str(sys.argv[1])

print("Generating set ...")

yearList = [1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011,
            2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019]
monthList = ['01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12']  # EXPLOIT, NVD, RAPID7
tableList = ["EXPLOIT_DB", "NVD", "RAPID7", "Snyk"]
script_dir = os.path.dirname(__file__)

for table in tableList:
    print("Now analyzing table: " + table)
    if table == "Snyk":
        monthList = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']  # snyk
    words = [[], [], []]  # structure is: [ [Month.Year], [999, 998, ...], ["test", "test2", ...] ]
    for year in yearList:
        for month in monthList:
            valueAdded = False
            rel_path = table + "/" + str(year) + "." + str(month) + ".cleaned" + ".csv"
            file = os.path.join(script_dir, rel_path)
            reader = open(file, encoding='utf8').read().splitlines()
            counter = 1
            for line in reader:
                if line == "":  # this only happens with malformed line breaks
                    continue
                lineBeforeComma, lineAfterComma = line.lower().split(",", 1)
                if (lineAfterComma == searchTerm):
                    words[0].append(str(year) + "/" + str(month))
                    words[1].append(lineBeforeComma)
                    words[2].append(lineAfterComma)
                    valueAdded = True
            if valueAdded == False:  # for ease of use in Excel
                words[0].append(str(year) + "/" + str(month))
                words[1].append("0")
                words[2].append(searchTerm)

    with open('Searches/' + table + "/" + searchTerm + '.csv', 'w+', encoding="utf8") as f:
        data = f.read()
        f.seek(0)
        for i in range(len(words[0])):
            f.write(words[0][i] + "," + words[1][i] + "," + words[2][i] + "\n")

print("Finished.")


# 核心思想：其实这题就是找多叉树的中心点，根据无环图的定义，中心点最多不会超过2（3个中心点就会形成环）
# 参考 https://leetcode.com/problems/minimum-height-trees/discuss/76132/Iterative-remove-leaves-Python-solution # 的代码
from collections import defaultdict
class Solution:
    def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]:
        dic = defaultdict(set)
        for u, v in edges:
            dic[u].add(v)
            dic[v].add(u)
        nodes = set(range(n))
        while len(nodes) > 2:
            leaves = set(i for i in nodes if len(dic[i]) == 1)
            for node in leaves:
                for pair_node in dic[node]: # 因为dic的values是集合，集合无法直接取出元素，所以不能写成dic[dic[node]].discard(node)
                    dic[pair_node].discard(node)
            nodes -= leaves
        return list(nodes)

# 直接BFS会超时
# from collections import defaultdict
# class Solution:
#     def findMinHeightTrees(self, n: int, edges: List[List[int]]) -> List[int]:
#         ret, m_dep = [], float('inf')
#         dic = defaultdict(set)
#         for u, v in edges:
#             dic[u].add(v)
#             dic[v].add(u)
#         def bfs(root):
#             check = [1] * n
#             check[root] = 0
#             dep = 1
#             layer = [root]
#             next_layer = []
#             while layer:
#                 t = layer.pop(0)
#                 for it in dic[t]:
#                     if check[it]:
#                         next_layer.append(it)
#                         check[it] = 0
#                 if not layer and next_layer:
#                     layer = next_layer
#                     next_layer = []
#                     dep += 1
#             return dep
#         for i in range(n):
#             dep = bfs(i)
#             if dep < m_dep:
#                 m_dep = dep
#                 ret = [i]
#             elif dep == m_dep:
#                 ret.append(i)
#         return ret


#!/usr/bin/env python3
# -*- coding utf-8 -*-

import cv2
import numpy as np
import torch
import matplotlib.pyplot as plt
from PIL import Image
from diffusers.utils import load_image
from diffusers import StableDiffusionControlNetPipeline, ControlNetModel

"""
使用ControlNet增强图像生成的稳定性
先用opencv获取头部轮廓，然后把图片换成明星脸

Canny，边缘检测
Open Pose，捕捉人体的动作
Scribble，以一个简单的简笔画为基础，生成精美的图片
HED Boundary，外一种边缘检测算法
Depth，深度估计，也就是对一张图片的前后深度估计出来的轮廓图
Normal Map，法线贴图，通常在游戏中用得比较多，可以在不增加模型复杂性的情况下，提升细节效果
Semantic Segmentation，语义分割图，可以把图片划分成不同的区域模块
M-LSD，这个能够获取图片中的直线段，很适合用来给建筑物或者房间内的布局描绘轮廓，这个算法也常常被用在自动驾驶里面
"""

def get_canny_image(original_image, low_threshold=100, high_threshold=200):
  image = np.array(original_image)

  image = cv2.Canny(image, low_threshold, high_threshold)
  image = image[:, :, None]
  image = np.concatenate([image, image, image], axis=2)
  canny_image = Image.fromarray(image)
  return canny_image

def display_images(image1, image2):
  # Combine the images horizontally
  combined_image = Image.new('RGB', (image1.width + image2.width, max(image1.height, image2.height)))
  combined_image.paste(image1, (0, 0))
  combined_image.paste(image2, (image1.width, 0))
  # Display the combined image
  plt.imshow(combined_image)
  plt.axis('off')
  plt.show()

def draw_image_grids(images, rows, cols):
  # Create a rows x cols grid for displaying the images
  fig, axes = plt.subplots(2, 2, figsize=(10, 10))

  for row in range(rows):
    for col in range(cols):
      axes[row, col].imshow(images[col + row * cols])
  for ax in axes.flatten():
      ax.axis('off')
  # Display the grid
  plt.show()

if __name__ == '__main__':
    # 使用opencv获取“戴珍珠耳环的少女”中的头像
    image_file = "data/input_image_vermeer.png"
    original_image = load_image(image_file)
    canny_image = get_canny_image(original_image)
    display_images(original_image, canny_image)

    # 加载模型
    controlnet = ControlNetModel.from_pretrained("lllyasviel/sd-controlnet-canny", torch_dtype=torch.float16)
    pipe = StableDiffusionControlNetPipeline.from_pretrained(
        "runwayml/stable-diffusion-v1-5", controlnet=controlnet, torch_dtype=torch.float16
    )

    # 显存不够用时，将模型从显存中移除
    pipe.enable_model_cpu_offload()
    # 通过xformers加速推理
    pipe.enable_xformers_memory_efficient_attention()

    # 同时生成四张图片，根据四位女星
    prompt = ", best quality, extremely detailed"
    prompt = [t + prompt for t in ["Audrey Hepburn", "Elizabeth Taylor", "Scarlett Johansson", "Taylor Swift"]]
    generator = [torch.Generator(device="cpu").manual_seed(42) for i in range(len(prompt))]

    # 计算并展示生成的四张图片
    output = pipe(
        prompt,
        canny_image,
        negative_prompt=["monochrome, lowres, bad anatomy, worst quality, low quality"] * 4,
        num_inference_steps=20,
        generator=generator,
    )
    draw_image_grids(output.images, 2, 2)


import os
import sys

project = 'qAuth'
copyright = '2019, Ravisankar A V'
author = 'Ravisankar A V'
version = '0.0.1'
templates_path = ['_templates']
source_suffix = '.rst'
master_doc = 'index'
pygments_style = 'sphinx'
html_theme = 'sphinx_rtd_theme'
html_static_path = ['_static']

sys.path.insert(0, os.path.abspath('../..'))
extensions = ['sphinx.ext.autodoc',]


import os
import importlib
import sys
import logging
import configparser

from .config import get_config
from ..utils import get_checkpoints
from ..trainer.tf_trainer_config import get_config as get_train_config

logger = logging.getLogger(__name__)

def get_base_project(path):
    if not os.path.exists(path):
        return get_base_project('.')

    dirname = os.path.abspath(os.path.dirname(path))
    while dirname != '/':
        try:
            subdirs = os.listdir(dirname)
        except (IOError, OSError):
            break
        if '.project' in subdirs:
            return dirname
        dirname = os.path.dirname(dirname)
    return None

def get_class(name, subtype, project, config):
    # Get class name
    split = name.split(':')
    if len(split) == 1:
        name = split[0]
        klass_name = subtype
    else:
        name = split[0]
        klass_name = split[1]

    # Check if name is path to file
    abspath = os.path.abspath(name)
    if os.path.exists(abspath):
        name, ext = os.path.splitext(abspath)
        if ext != '.py':
            msg = '{} is being loaded from a non-python file: {}'
            msg = msg.format(subtype.title(), abspath)
            raise IOError(msg)

        klass = extract_class(name, klass_name)
    else:
        if project is None:
            msg = 'No path to {subtype} file :{file}: was given'
            msg += ' and training is not being done inside'
            msg += ' a project.'
            msg = msg.format(subtype=subtype, file=name)
            raise IOError(msg)

        if subtype == 'architecture':
            subdir = config['structure']['architectures_dir']
        elif subtype == 'loss':
            subdir = config['structure']['losses_dir']
        elif subtype == 'dataset':
            subdir = config['structure']['datasets_dir']
        elif subtype == 'metric':
            subdir = config['structure']['metrics_dir']

        path = os.path.join(project, subdir, name)
        klass = extract_class(path, klass_name)

    return klass

def extract_class(path, name):
    basename = os.path.basename(path)
    abspath = os.path.dirname(os.path.abspath(path))
    sys.path.insert(0, abspath)
    module = importlib.import_module(basename)
    try:
        klass = getattr(module, name)
    except AttributeError:
        msg = 'Python module {} does not have a class named {}'
        msg = msg.format(os.path.basename(path), name)
        raise AttributeError(msg)
    return klass

def get_all_models():
    project = get_base_project('.')

    # Get configuration
    config_path = None
    if project is not None:
        config_path = os.path.join(
            project, '.project', 'axon_config.ini')
    else:
        return []
    config = get_config(path=config_path)
    models_dir = config['structure']['models_dir']
    return os.listdir(models_dir)

def get_model_path(name, project, config):
    models_dir = config['structure']['models_dir']
    return os.path.join(project, models_dir, name)

def load_model(name=None, path=None):
    if (name is None) and (path is None):
        raise ValueError('Name or path must be supplied')

    if path is None:
        project = get_base_project('.')

    if name is None:
        name = os.path.basename(path)

    config_path = os.path.join(
        project, '.project', 'axon_config.ini')
    config = get_config(path=config_path)

    if path is None:
        path = os.path.join(
            project, config['structure']['models_dir'], name)

    model_file = config['configurations']['model_specs']
    model_config = configparser.ConfigParser()
    model_config.read([
        os.path.join(project, '.project', model_file),
        os.path.join(path, model_file)])

    architecture_name = model_config['model']['architecture']

    # Read classes
    model = get_class(
        architecture_name,
        'architecture',
        project,
        config)()

    project_train_config_path = os.path.join(
        project,
        '.project',
        config['configurations']['train_configs'])
    train_config_path = os.path.join(
        path,
        config['configurations']['train_configs'])
    train_config = get_train_config(
        paths=[project_train_config_path, train_config_path]).config
    tf_subdir = train_config['checkpoints']['tensorflow_checkpoints_dir']
    npy_subdir = train_config['checkpoints']['numpy_checkpoints_dir']
    ckpt = get_checkpoints(
        path,
        tf_subdir=tf_subdir,
        npy_subdir=npy_subdir)

    if ckpt is not None:
        ckpt_type, ckpt_path, ckpt = ckpt
        model.ckpt_type = ckpt_type
        model.ckpt_path = ckpt_path

    return model


# Version: 2.3
# Author: Miguel Martinez Lopez
# Uncomment the next line to see my email
# print("Author's email: %s"%"61706c69636163696f6e616d656469646140676d61696c2e636f6d".decode("hex"))

import platform

try:
    from Tkinter import Frame, BOTH ,N,E,S, W, CENTER, Entry, Canvas, Label
    from tkFont import Font, nametofont
    from ttk import Treeview, Scrollbar, Style
except ImportError:
    from tkinter import Frame, BOTH ,N,E,S, W, CENTER, Entry, Canvas, Label
    from tkinter.font import Font, nametofont
    from tkinter.ttk import Treeview, Scrollbar, Style

# Python 3 compatibility
try:
  basestring
except NameError:
  basestring = str

class Row(object):
    def __init__(self, table, index):
        self._multicolumn_listbox = table
        self._index = index

    def data(self):
        return self._multicolumn_listbox.row_data(self._index)

    def delete(self):
        self._multicolumn_listbox.delete_row(self._index)

    def update(self, data):
        self._multicolumn_listbox.update_row(self._index, data)

    def select(self):
        self._multicolumn_listbox.select_row(self._index)

    def deselect(self):
        self._multicolumn_listbox.deselect_row(self._index)

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

    def __len__(self):
        return self._multicolumn_listbox.number_of_columns

class Column(object):
    def __init__(self, table, index):
        self._multicolumn_listbox = table
        self._index = index

    def data(self):
        return self._multicolumn_listbox.column_data(self._index)

    def delete(self):
        self._multicolumn_listbox.delete_column(self._index)

    def update(self, data):
        self._multicolumn_listbox.update_column(self._index, data)

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

    def __len__(self):
        return self._multicolumn_listbox.number_of_rows

class Multicolumn_Listbox(object):
    _style_index = 0

    class List_Of_Rows(object):
        def __init__(self, multicolumn_listbox):
            self._multicolumn_listbox = multicolumn_listbox

        def data(self, index):
            return self._multicolumn_listbox.row_data(index)

        def get(self, index):
            return Row(self._multicolumn_listbox, index)

        def insert(self, data, index=None):
            self._multicolumn_listbox.insert_row(data, index)

        def delete(self, index):
            self._multicolumn_listbox.delete_row(index)

        def update(self, index, data):
            self._multicolumn_listbox.update_row(index, data)

        def select(self, index):
            self._multicolumn_listbox.select_row(index)

        def deselect(self, index):
            self._multicolumn_listbox.deselect_row(index)

        def set_selection(self, indices):
            self._multicolumn_listbox.set_selection(indices)

        def __getitem__(self, index):
            return self.get(index)

        def __setitem__(self, index, value):
            return self._multicolumn_listbox.update_row(index, value)

        def __delitem__(self, index):
            self._multicolumn_listbox.delete_row(index)

        def __len__(self):
            return self._multicolumn_listbox.number_of_rows

    class List_Of_Columns(object):
        def __init__(self, multicolumn_listbox):
            self._multicolumn_listbox = multicolumn_listbox

        def data(self, index):
            return self._multicolumn_listbox.get_column(index)

        def get(self, index):
            return Column(self._multicolumn_listbox, index)

        def delete(self, index):
            self._multicolumn_listbox.delete_column(index)

        def update(self, index, data):
            self._multicolumn_listbox.update_column(index, data)

        def __getitem__(self, index):
            return self.get(index)

        def __setitem__(self, index, value):
            return self._multicolumn_listbox.update_column(index, value)

        def __delitem__(self, index):
            self._multicolumn_listbox.delete_column(index)

        def __len__(self):
            return self._multicolumn_listbox.number_of_columns

    def __init__(self, master, columns, data=None, command=None, sort=True, select_mode=None, heading_anchor = CENTER, cell_anchor=W, style=None, height=None, padding=None, adjust_heading_to_content=False, stripped_rows=None, selection_background=None, selection_foreground=None, field_background=None, heading_font= None, heading_background=None, heading_foreground=None, cell_pady=2, cell_background=None, cell_foreground=None, cell_font=None, headers=True):

        self._stripped_rows = stripped_rows

        self._columns = columns

        self._number_of_rows = 0
        self._number_of_columns = len(columns)

        self.row = self.List_Of_Rows(self)
        self.column = self.List_Of_Columns(self)

        s = Style()

        if style is None:
            style_name = "Multicolumn_Listbox%s.Treeview"%self._style_index
            self._style_index += 1
        else:
            style_name = style

        style_map = {}
        if selection_background is not None:
            style_map["background"] = [('selected', selection_background)]

        if selection_foreground is not None:
            style_map["foeground"] = [('selected', selection_foreground)]

        if style_map:
            s.map(style_name, **style_map)

        style_config = {}
        if cell_background is not None:
            style_config["background"] = cell_background

        if cell_foreground is not None:
            style_config["foreground"] = cell_foreground

        if cell_font is None:
            font_name = s.lookup(style_name, "font")
            cell_font = nametofont(font_name)
        else:
            if not isinstance(cell_font, Font):
                if isinstance(cell_font, basestring):
                    cell_font = nametofont(cell_font)
                else:
                    if len(font) == 1:
                        cell_font = Font(family=cell_font[0])
                    elif len(font) == 2:
                        cell_font = Font(family=cell_font[0], size=cell_font[1])

                    elif len(font) == 3:
                        cell_font = Font(family=cell_font[0], size=cell_font[1], weight=cell_font[2])
                    else:
                        raise ValueError("Not possible more than 3 values for font")

            style_config["font"] = cell_font

        self._cell_font = cell_font

        self._rowheight = cell_font.metrics("linespace")+cell_pady
        style_config["rowheight"]=self._rowheight

        if field_background is not None:
            style_config["fieldbackground"] = field_background

        s.configure(style_name, **style_config)

        heading_style_config = {}
        if heading_font is not None:
            heading_style_config["font"] = heading_font
        if heading_background is not None:
            heading_style_config["background"] = heading_background
        if heading_foreground is not None:
            heading_style_config["foreground"] = heading_foreground

        heading_style_name = style_name + ".Heading"
        s.configure(heading_style_name, **heading_style_config)

        treeview_kwargs = {"style": style_name}

        if height is not None:
            treeview_kwargs["height"] = height

        if padding is not None:
            treeview_kwargs["padding"] = padding

        if headers:
            treeview_kwargs["show"] = "headings"
        else:
            treeview_kwargs["show"] = ""

        if select_mode is not None:
            treeview_kwargs["selectmode"] = select_mode

        self.interior = Treeview(master, columns=columns, **treeview_kwargs)

        if command is not None:
            self._command = command
            self.interior.bind("<<TreeviewSelect>>", self._on_select)

        for i in range(0, self._number_of_columns):

            if sort:
                self.interior.heading(i, text=columns[i], anchor=heading_anchor, command=lambda col=i: self.sort_by(col, descending=False))
            else:
                self.interior.heading(i, text=columns[i], anchor=heading_anchor)

            if adjust_heading_to_content:
                self.interior.column(i, width=Font().measure(columns[i]))

            self.interior.column(i, anchor=cell_anchor)

        if data is not None:
            for row in data:
                self.insert_row(row)

    @property
    def row_height(self):
        return self._rowheight

    @property
    def font(self):
        return self._cell_font

    def configure_column(self, index, width=None, minwidth=None, anchor=None, stretch=None):
        kwargs = {}
        for config_name in ("width", "anchor", "stretch", "minwidth"):
            config_value = locals()[config_name]
            if config_value is not None:
                kwargs[config_name] = config_value

        self.interior.column('#%s'%(index+1), **kwargs)

    def row_data(self, index):
        try:
            item_ID = self.interior.get_children()[index]
        except IndexError:
            raise ValueError("Row index out of range: %d"%index)

        return self.item_ID_to_row_data(item_ID)

    def update_row(self, index, data):
        try:
            item_ID = self.interior.get_children()[index]
        except IndexError:
            raise ValueError("Row index out of range: %d"%index)

        if len(data) == len(self._columns):
            self.interior.item(item_ID, values=data)
        else:
            raise ValueError("The multicolumn listbox has only %d columns"%self._number_of_columns)

    def delete_row(self, index):
        list_of_items = self.interior.get_children()

        try:
            item_ID = list_of_items[index]
        except IndexError:
            raise ValueError("Row index out of range: %d"%index)

        self.interior.delete(item_ID)
        self._number_of_rows -= 1

        if self._stripped_rows:
            for i in range(index, self._number_of_rows):
                self.interior.tag_configure(list_of_items[i+1], background=self._stripped_rows[i%2])

    def insert_row(self, data, index=None):
        if len(data) != self._number_of_columns:
            raise ValueError("The multicolumn listbox has only %d columns"%self._number_of_columns)

        if index is None:
            index = self._number_of_rows-1

        item_ID = self.interior.insert('', index, values=data)
        self.interior.item(item_ID, tags=item_ID)

        self._number_of_rows += 1

        if self._stripped_rows:
            list_of_items = self.interior.get_children()

            self.interior.tag_configure(item_ID, background=self._stripped_rows[index%2])

            for i in range(index+1, self._number_of_rows):
                self.interior.tag_configure(list_of_items[i], background=self._stripped_rows[i%2])

    def column_data(self, index):
        return [self.interior.set(child_ID, index) for child_ID in self.interior.get_children('')]

    def update_column(self, index, data):
        for i, item_ID in enumerate(self.interior.get_children()):
            data_row = self.item_ID_to_row_data(item_ID)
            data_row[index] = data[i]

            self.interior.item(item_ID, values=data_row)

        return data

    def clear(self):
        # Another possibility:
        #  self.interior.delete(*self.interior.get_children())

        for row in self.interior.get_children():
            self.interior.delete(row)

        self._number_of_rows = 0

    def update(self, data):
        self.clear()

        for row in data:
            self.insert_row(row)

    def focus(self, index=None):
        if index is None:
            return self.interior.item(self.interior.focus())
        else:
            item = self.interior.get_children()[index]
            self.interior.focus(item)

    def state(self, state=None):
        if stateSpec is None:
            return self.interior.state()
        else:
            self.interior.state(state)

    @property
    def number_of_rows(self):
        return self._number_of_rows

    @property
    def number_of_columns(self):
        return self._number_of_columns

    def toogle_selection(self, index):
        list_of_items = self.interior.get_children()

        try:
            item_ID = list_of_items[index]
        except IndexError:
            raise ValueError("Row index out of range: %d"%index)

        self.interior.selection_toggle(item_ID)

    def select_row(self, index):
        list_of_items = self.interior.get_children()

        try:
            item_ID = list_of_items[index]
        except IndexError:
            raise ValueError("Row index out of range: %d"%index)

        self.interior.selection_add(item_ID)

    def deselect_row(self, index):
        list_of_items = self.interior.get_children()

        try:
            item_ID = list_of_items[index]
        except IndexError:
            raise ValueError("Row index out of range: %d"%index)

        self.interior.selection_remove(item_ID)

    def deselect_all(self):
        self.interior.selection_remove(self.interior.selection())

    def set_selection(self, indices):
        list_of_items = self.interior.get_children()

        self.interior.selection_set(" ".join(list_of_items[row_index] for row_index in indices))

    @property
    def selected_rows(self):
        data = []
        for item_ID in self.interior.selection():
            data_row = self.item_ID_to_row_data(item_ID)
            data.append(data_row)

        return data

    @property
    def indices_of_selected_rows(self):
        list_of_indices = []
        for index, item_ID in enumerate(self.interior.get_children()):
            if item_ID in self.interior.selection():
                list_of_indices.append(index)

        return list_of_indices

    def delete_all_selected_rows(self):
        selected_items = self.interior.selection()
        for item_ID in selected_items:
            self.interior.delete(item_ID)

        number_of_deleted_rows = len(selected_items)
        self._number_of_rows -= number_of_deleted_rows

        return number_of_deleted_rows

    def _on_select(self, event):
        for item_ID in event.widget.selection():
            data_row = self.item_ID_to_row_data(item_ID)
            self._command(data_row)

    def item_ID_to_row_data(self, item_ID):
        item = self.interior.item(item_ID)
        return item["values"]

    @property
    def table_data(self):
        data = []

        for item_ID in self.interior.get_children():
            data_row = self.item_ID_to_row_data(item_ID)
            data.append(data_row)

        return data

    @table_data.setter
    def table_data(self, data):
        self.update(data)

    def cell_data(self, row, column):
        """Get the value of a table cell"""
        try:
            item = self.interior.get_children()[row]
        except IndexError:
            raise ValueError("Row index out of range: %d"%row)

        return self.interior.set(item, column)

    def update_cell(self, row, column, value):
        """Set the value of a table cell"""

        item_ID = self.interior.get_children()[row]

        data = self.item_ID_to_row_data(item_ID)

        data[column] = value
        self.interior.item(item_ID, values=data)

    def __getitem__(self, index):
        if isinstance(index, tuple):
            row, column = index
            return self.cell_data(row, column)
        else:
            raise Exception("Row and column indices are required")

    def __setitem__(self, index, value):
        if isinstance(index, tuple):
            row, column = index
            self.update_cell(row, column, value)
        else:
            raise Exception("Row and column indices are required")

    def bind(self, event, handler):
        self.interior.bind(event, handler)

    def sort_by(self, col, descending):
        """
        sort tree contents when a column header is clicked
        """
        # grab values to sort
        data = [(self.interior.set(child_ID, col), child_ID) for child_ID in self.interior.get_children('')]

        # if the data to be sorted is numeric change to float
        try:
            data = [(float(number), child_ID) for number, child_ID in data]
        except ValueError:
            pass

        # now sort the data in place
        data.sort(reverse=descending)
        for idx, item in enumerate(data):
            self.interior.move(item[1], '', idx)

        # switch the heading so that it will sort in the opposite direction
        self.interior.heading(col, command=lambda col=col: self.sort_by(col, not descending))

        if self._stripped_rows:
            list_of_items = self.interior.get_children('')
            for i in range(len(list_of_items)):
                self.interior.tag_configure(list_of_items[i], background=self._stripped_rows[i%2])

    def destroy(self):
        self.interior.destroy()

    def item_ID(self, index):
        return self.interior.get_children()[index]

################################################
# The next code is only for Tk_Table class
#
#

OS = platform.system()

def bind_function_onMouseWheel(scrolled_widget, orient, binding_widget=None, callback=None, factor = 1, unit="units"):
    if not scrolled_widget:
        binding_widget = scrolled_widget

    view_command = getattr(scrolled_widget, orient+'view')

    if OS == 'Linux':
        if callback:
            def onMouseWheel(event):
                if event.num == 4:
                    view_command("scroll",(-1)*factor, unit)
                elif event.num == 5:
                    view_command("scroll",factor, unit)

                callback()
        else:
            def onMouseWheel(event):
                if event.num == 4:
                    view_command("scroll",(-1)*factor, unit)
                elif event.num == 5:
                    view_command("scroll",factor, unit)

    elif OS == 'Windows':
        if callback:
            def onMouseWheel(event):
                view_command("scroll",(-1)*int((event.delta/120)*factor), unit)
                callback()
        else:
            def onMouseWheel(event):
                view_command("scroll",(-1)*int((event.delta/120)*factor), unit)

    elif OS == 'Darwin':
        if callback:
            def onMouseWheel(event):
                view_command("scroll",event.delta, unit)
                callback()
        else:
            def onMouseWheel(event):
                view_command("scroll",event.delta, unit)

    if OS == "Linux" :
        binding_widget.bind('<4>', onMouseWheel, add='+')
        binding_widget.bind('<5>', onMouseWheel, add='+')
    else:
        # Windows and MacOS
        binding_widget.bind("<MouseWheel>", onMouseWheel,  add='+')

def unbind_function_onMouseWheel(binding_widget):
    if OS == "Linux" :
        binding_widget.unbind('<4>')
        binding_widget.unbind('<5>')
    else:
        binding_widget.unbind('<MouseWheel>')

class Row_Header(Canvas):
    def __init__(self, master, font, row_height, row_minwidth, hover_background=None, background=None, anchor=None, onclick=None):
        Canvas.__init__(self, master, bd=0, highlightthickness=0, yscrollincrement=row_height, width=0)

        if background is not None:
            self.configure(background=background)

        self._frame_of_row_numbers = Frame(self, bd=0)

        self.create_window(0, 0, window=self._frame_of_row_numbers, anchor=N+W)
        self._width_of_row_numbers = 0

        self._labelrow_kwargs = labelrow_kwargs= {}

        labelrow_kwargs["font"] = font

        if anchor is not None:
            labelrow_kwargs["anchor"] = anchor

        self._row_minwidth= row_minwidth
        self._row_height = row_height
        self._hover_background = hover_background
        self._onclick = onclick

        self._number_of_labels = 0

    def pop(self, n_labels=1):
        if self._number_of_labels == 0:
            return

        list_of_slaves = self._frame_of_row_numbers.pack_slaves()

        for i in range(n_labels):
            list_of_slaves.pop().destroy()

        if list_of_slaves:
            width_of_row_numbers= max(list_of_slaves[-1].winfo_reqwidth(), self._row_minwidth)

            if width_of_row_numbers < self._width_of_row_numbers:
                self._width_of_row_numbers = width_of_row_numbers

                for slave in self._frame_of_row_numbers.pack_slaves():
                    slave.configure(width=width_of_row_numbers)

        self.config(width=self._frame_of_row_numbers.winfo_reqwidth())
        self.config(scrollregion=(0, 0, self._frame_of_row_numbers.winfo_reqwidth(), len(list_of_slaves)*self._row_height))

        self._number_of_labels -= n_labels

    def delete_labels(self):
        if self._number_of_labels == 0:
            return

        for slave in self._frame_of_row_numbers.pack_slaves():
            slave.destroy()

        self._number_of_labels = 0

    def new_label(self):
        # Creating a new row header

        self._number_of_labels += 1

        frame_button = Frame(self._frame_of_row_numbers, height=self._row_height)
        frame_button.pack()
        frame_button.pack_propagate(False)

        row_label = Label(frame_button, text =self._number_of_labels, **self._labelrow_kwargs)
        row_label.bind("<1>", lambda event, index=self._number_of_labels-1: self._on_click_label(index))

        if self._hover_background:
            row_label.bind("<Enter>", lambda event, row_label=row_label: row_label.configure(background=self._hover_background))
            row_label.bind("<Leave>", lambda event, row_label=row_label: row_label.configure(background=self.cget("background")))

        row_label.pack(expand=True, fill=BOTH)

        width_of_row_numbers= max(row_label.winfo_reqwidth(), self._row_minwidth)

        if width_of_row_numbers > self._width_of_row_numbers:
            self._width_of_row_numbers = width_of_row_numbers

            for slave in self._frame_of_row_numbers.pack_slaves():
                slave.configure(width=width_of_row_numbers)
        else:
            frame_button.configure(width=width_of_row_numbers)

        frame_button.update_idletasks()

        self.config(width=self._frame_of_row_numbers.winfo_reqwidth())
        self.config(scrollregion=(0, 0, self._frame_of_row_numbers.winfo_reqwidth(), self._number_of_labels*self._row_height))

    def _on_click_label(self, index):
        if self._onclick:
            self._onclick(index)

class Tk_Table(Frame, object):
    def __init__(self, master, columns, data=None, command=None, editable=True, sort=True, select_mode=None, autoscroll=True, vscrollbar=True, hscrollbar=False, heading_anchor = CENTER, cell_anchor=W, style=None, scrollbar_background=None, scrollbar_troughcolor=None, height=None, padding=None, adjust_heading_to_content=False, stripped_rows=None, selection_background=None, selection_foreground=None, cell_background=None, cell_foreground=None, cell_font=None, field_background=None, heading_font= None, heading_background=None, heading_foreground=None, cell_pady=2, column_header=True, row_numbers=True, entry_background="#d6d6d6", entry_foreground=None, entry_validatecommand=None, entry_selectbackground="#1BA1E2", entry_selectborderwidth=None, entry_selectforeground=None, entry_font = "TkDefaultFont", rowlabel_anchor=E, rowlabel_minwidth=0, rowlabel_hoverbackground="#FFFFFF",frame_relief=None, frame_borderwidth=None, frame_background=None):

        frame_kwargs = {}

        if frame_relief is not None:
            frame_kwargs["relief"] = frame_relief

        if frame_borderwidth is not None:
            frame_kwargs["borderwidth"] = frame_borderwidth

        if frame_background is not None:
            frame_kwargs["background"] = frame_background

        Frame.__init__(self, master, class_="Multicolumn_Listbox", **frame_kwargs)

        self.grid_rowconfigure(0, weight=1)
        self.grid_columnconfigure(1, weight=1)

        self._multicolumn_listbox = Multicolumn_Listbox(self, columns, data=data, command=command, sort=sort, select_mode=select_mode, heading_anchor = heading_anchor, cell_anchor=cell_anchor, style=style, height=height, padding=padding, adjust_heading_to_content=adjust_heading_to_content, stripped_rows=stripped_rows, selection_background=selection_background, selection_foreground=selection_foreground, cell_background=cell_background, cell_foreground=cell_foreground, cell_font=cell_font, field_background=field_background, heading_font=heading_font, heading_background=heading_background, heading_foreground=heading_foreground, cell_pady=cell_pady, headers=column_header)
        self._multicolumn_listbox.interior.grid(row=0, column=1, sticky= N+E+W+S)

        self._mousewheel_detection = True

        if row_numbers:
            self._row_numbers = Row_Header(self, font=self._multicolumn_listbox.font, row_height=self._multicolumn_listbox.row_height, row_minwidth=rowlabel_minwidth, hover_background = rowlabel_hoverbackground, anchor=rowlabel_anchor, onclick=self._on_click_row_label)
            self._row_numbers.grid(row=0, column=0, sticky= N+S+E)

            self._multicolumn_listbox.interior.bind("<Map>", self._place_vertically_row_numbers)
        else:
            self._row_numbers = None

        if editable:
            self._selected_cell = None
            self._entry_popup = None

            self._multicolumn_listbox.interior.bind("<1>", self._edit_cell)

            def configure(event):
                """
                if self._entry_popup:
                    self._entry_popup.destroy()
                return
                """

                self._multicolumn_listbox.interior.update_idletasks()
                self._update_position_of_entry()

            self._multicolumn_listbox.interior.bind("<Configure>", configure)

            self._entry_kwargs = entry_kwargs = {}
            if entry_background is not None:
                entry_kwargs["background"] = entry_background

            if entry_foreground is not None:
                entry_kwargs["foreground"] = entry_foreground

            if entry_validatecommand is not None:
                entry_kwargs["validatecommand"] = entry_validatecommand

            if entry_selectbackground is not None:
                entry_kwargs["selectbackground"] = entry_selectbackground

            if entry_selectforeground is not None:
                entry_kwargs["selectforeground"] = entry_selectforeground

            if entry_font is not None:
                entry_kwargs["font"] = entry_font

        if command is not None:
            self._command = command
            self._multicolumn_listbox.interior.bind("<<TreeviewSelect>>", self._on_select)

        scrollbar_kwargs = {}
        if scrollbar_background is not None:
            scrollbar_kwargs["background"] = scrollbar_background

        if scrollbar_troughcolor is not None:
            scrollbar_kwargs["throughcolor"] = scrollbar_troughcolor

        if vscrollbar:
            if editable:
                if row_numbers:
                    def yview_command(*args):

                        self._multicolumn_listbox.interior.yview(*args)
                        self._row_numbers.yview(*args)

                        self._update_position_of_entry()
                else:
                    def yview_command(*args):
                        self._multicolumn_listbox.interior.yview(*args)
                        self._update_position_of_entry()
            else:
                if row_numbers:
                    def yview_command(*args):
                        self._multicolumn_listbox.interior.yview(*args)
                        self._row_numbers.yview(*args)
                else:
                    yview_command = self._multicolumn_listbox.interior.yview

            self._vbar=Scrollbar(self,takefocus=0, command=yview_command, **scrollbar_kwargs)
            self._vbar.grid(row=0, column=2, sticky= N+S)

            def yscrollcommand(first,last):
                first, last = float(first), float(last)
                if first <= 0 and last >= 1:
                    if self._mousewheel_detection:
                        if autoscroll:
                            self._vbar.grid_remove()

                        if row_numbers:
                            unbind_function_onMouseWheel(self._multicolumn_listbox.interior)
                        self._mousewheel_detection = False
                else:
                    if not self._mousewheel_detection:
                        if autoscroll:
                            self._vbar.grid()

                        if row_numbers:
                            bind_function_onMouseWheel(self._row_numbers, "y", binding_widget=self._multicolumn_listbox.interior, unit="pages")
                        self._mousewheel_detection = True

                self._vbar.set(first, last)
                if editable:
                    self._update_position_of_entry()

            self._multicolumn_listbox.interior.config(yscrollcommand=yscrollcommand)

        if hscrollbar:
            if editable:
                def xview_command(*args):
                    self._multicolumn_listbox.interior.xview(*args)
                    self._update_position_of_entry()
            else:
                xview_command = self._multicolumn_listbox.interior.xview

            self._hbar=Scrollbar(self,takefocus=0, command=xview_command, **scrollbar_kwargs)
            self._hbar.grid(row=1, column=1, sticky= E+W)

            if autoscroll:
                if editable:
                    def xscrollcommand(f,l, self=self):
                        make_autoscroll(self._hbar, f, l)
                        self._update_position_of_entry()
                else:
                    def xscrollcommand(f,l, hbar=self._hbar):
                        make_autoscroll(hbar, f, l)

                self._multicolumn_listbox.interior.config(xscrollcommand=xscrollcommand)
            else:
                self._multicolumn_listbox.interior.config(xscrollcommand=self._hbar.set)

    def _place_vertically_row_numbers(self, event):
        self._multicolumn_listbox.interior.unbind("<Map>")

        item_ID = self._multicolumn_listbox.interior.insert('', 0, values=[""]*self._multicolumn_listbox.number_of_columns)
        self._multicolumn_listbox.interior.update()
        x,y,w,h = self._multicolumn_listbox.interior.bbox(item_ID)
        self._multicolumn_listbox.interior.delete(item_ID)

        self._row_numbers.grid_configure(pady=(y,0))

    def _edit_cell(self, event):
        '''Executed, when a row is clicked. Opens an entry popup above the cell, so it is possible
        to select text '''

        # close previous popups
        if self._entry_popup:
            self._destroy_entry()

        # what row and column was clicked on
        item_ID = self._multicolumn_listbox.interior.identify_row(event.y)
        if not item_ID: return

        column = self._multicolumn_listbox.interior.identify_column(event.x)

        if column == "": return

        # get column position info
        x,y,width,height = self._multicolumn_listbox.interior.bbox(item_ID, column)

        # place Entry popup properly
        column_number = int(column[1:])-1
        cell_data = self._multicolumn_listbox.item_ID_to_row_data(item_ID)[column_number]

        self._entry_popup = Entry(self._multicolumn_listbox.interior, exportselection=True, borderwidth=0,  **self._entry_kwargs)
        self._entry_popup.place(x=x, y=y, width=width, height=height)

        self._entry_popup.insert(0, cell_data)
        self._entry_popup.focus_force()

        self._entry_popup.bind("<Control-a>", lambda event: self._select_all_entry_data)
        self._entry_popup.bind("<Escape>", lambda event: self._destroy_entry())
        self._entry_popup.bind("<FocusOut>", lambda event: self._destroy_entry())

        bind_function_onMouseWheel(self._multicolumn_listbox.interior, "y", binding_widget=self._entry_popup, callback=self._update_position_of_entry, unit="pages")

        if self._row_numbers:
            bind_function_onMouseWheel(self._row_numbers, "y", binding_widget=self._entry_popup, unit="pages")

        self._entry_popup.bind("<Return>", self._on_update_cell)

        self._selected_cell = item_ID, column, column_number

    def _on_click_row_label(self, index):
        if self._selected_cell and self._multicolumn_listbox.item_ID(index) == self._selected_cell[0]:
            self._destroy_entry()

        self._multicolumn_listbox.toogle_selection(index)

    def _select_all_entry_data(self):
        ''' Set selection on the whole text '''
        self._entry_popup.selection_range(0, 'end')

        # returns 'break' to interrupt default key-bindings
        return 'break'

    def _destroy_entry(self):
        self._entry_popup.destroy()

        self._entry_popup = None
        self._selected_cell = None

    def _on_update_cell(self, event):
        item_ID, column, column_number = self._selected_cell

        data = self._entry_popup.get()

        row_data = self._multicolumn_listbox.item_ID_to_row_data(item_ID)
        row_data[column_number] = data
        self._multicolumn_listbox.interior.item(item_ID, values=row_data)

        self._destroy_entry()

    def _update_position_of_entry(self):
        if self._selected_cell:
            bbox = self._multicolumn_listbox.interior.bbox(self._selected_cell[0], self._selected_cell[1])
            if bbox == "":
                self._entry_popup.place_forget()
            else:
                x,y,width,height = bbox
                self._entry_popup.place(x=x, y=y, width=width, height=height)

    def configure_column(self, index, width=None, minwidth=None, anchor=None, stretch=None):
        self._multicolumn_listbox.configure_column(self, index, width=None, minwidth=None, anchor=None, stretch=None)

    def row_data(self, index):
        return self._multicolumn_listbox.row_data(index)

    def update_row(self, index, data):
        self._multicolumn_listbox.update_row(index,data)

    def delete_row(self, index):
        self._multicolumn_listbox.delete_row(index)
        if self._row_numbers:
            self._row_numbers.pop()

    def insert_row(self, data, index=None):
        self._multicolumn_listbox.insert_row(data, index)
        if self._row_numbers:
            self._row_numbers.new_label()

    def column_data(self, index):
        return self._multicolumn_listbox.column_data(index)

    def update_column(self, index, data):
        self._multicolumn_listbox.update_column(index, data)

    def clear(self):
        self._multicolumn_listbox.clear()

        if self._row_numbers:
            self._row_numbers.delete_labels()

    def update(self, data):
        current_number_of_rows = self._multicolumn_listbox.number_of_rows
        self._multicolumn_listbox.update(data)

        if self._row_numbers:
            number_of_rows = len(data)
            if current_number_of_rows < number_of_rows:
                for i in range(number_of_rows - current_number_of_rows):
                    self._row_numbers.new_label()
            else:
                n_labels = current_number_of_rows - number_of_rows
                self._row_numbers.pop(n_labels =n_labels)

    def focus(self, index=None):
        self._multicolumn_listbox.focus(index)

    def state(self, state=None):
        self._multicolumn_listbox.state(state)

    @property
    def number_of_rows(self):
        return self._multicolumn_listbox.number_of_rows

    @property
    def number_of_columns(self):
        return self._multicolumn_listbox.number_of_columns

    def toogle_selection(self, index):
        self._multicolumn_listbox.toogle_selection(index)

    def select_row(self, index):
        self._multicolumn_listbox.select_row(index)

    def deselect_row(self, index):
        self._multicolumn_listbox.deselect_row(index)

    def deselect_all(self):
        self._multicolumn_listbox.deselect_all()

    def set_selection(self, indices):
        self._multicolumn_listbox.set_selection(indices)

    @property
    def selected_rows(self):
        return self._multicolumn_listbox.selected_rows

    @property
    def indices_of_selected_rows(self):
        return self._multicolumn_listbox.indices_of_selected_rows

    def delete_all_selected_rows(self):
        number_of_deleted_rows = self._multicolumn_listbox.delete_all_selected_rows()

        if self._row_numbers:
            self._row_numbers.pop(n_labels=number_of_deleted_rows)

    @property
    def table_data(self):
        return self._multicolumn_listbox.table_data

    @table_data.setter
    def table_data(self, data):
        self.update(data)

    def cell_data(self, row, column):
        return self._multicolumn_listbox.cell_data(row, column)

    def update_cell(self, row, column, value):
        self._multicolumn_listbox.update_cell(row, column, value)

    def __getitem__(self, index):
        return self._multicolumn_listbox[index]

    def __setitem__(self, index, value):
        self._multicolumn_listbox[index] = value

    def bind(self, event, handler):
        self._multicolumn_listbox.bind(event, handler)

    def sort_by(self, col, descending):
        self._multicolumn_listbox.sort_by(col, descending)

if __name__ == '__main__':
    try:
        from Tkinter import Tk
        import tkMessageBox as messagebox
    except ImportError:
        from tkinter import Tk
        from tkinter import messagebox

    root = Tk()

    def on_select(data):
        print("called command when row is selected")
        print(data)
        print("\n")

    def show_info(msg):
        messagebox.showinfo("Table Data", msg)

    mcListbox = Multicolumn_Listbox(root, ["column one","column two", "column three"], command=on_select, cell_anchor="center")
    mcListbox.interior.pack()

    mcListbox.insert_row([1,2,3])
    show_info("mcListbox.insert_row([1,2,3])")

    mcListbox.row.insert([4,5,7])
    show_info("mcListbox.row.insert([4,5,7])")

    mcListbox.update_row(0, [7,8,9])
    show_info("mcListbox.update_row(0, [4,5,6])")

    mcListbox.update([[1,2,3], [4,5,6]])
    show_info("mcListbox.update([[1,2,3], [4,5,6]])")

    mcListbox.select_row(0)
    show_info("mcListbox.select_row(0)")

    print("mcListbox.selected_rows")
    print(mcListbox.selected_rows)
    print("\n")

    print("mcListbox.table_data")
    print(mcListbox.table_data)
    print("\n")

    print("mcListbox.row[0]")
    print(mcListbox.row[0])
    print("\n")

    print("mcListbox.row_data(0)")
    print(mcListbox.row_data(0))
    print("\n")

    print("mcListbox.column[1]")
    print(mcListbox.column[1])
    print("\n")

    print("mcListbox[0,1]")
    print(mcListbox[0,1])
    print("\n")

    mcListbox.column[1] = ["item1", "item2"]

    mcListbox.update_column(2, [8,9])
    show_info("mcListbox.update_column(2, [8,9])")

    mcListbox.clear()
    show_info("mcListbox.clear()")

    mcListbox.table_data = [[1,2,3], [4,5,6], [7,8,9]]
    show_info("mcListbox.table_data = [[1,2,3], [4,5,6], [7,8,9]]")

    mcListbox.delete_row(1)
    show_info("mcListbox.delete_row(1)")

    row = mcListbox.row[0].update([2,4,5])
    show_info("mcListbox.row[0].update([2,4,5])")

    mcListbox.destroy()

    # The next table is editable: Click on the table to edit the cell
    table = Tk_Table(root, ["column one","column two", "column three"], row_numbers=True, stripped_rows = ("white","#f2f2f2"), select_mode="none")
    table.pack(expand=True, fill=BOTH)

    table.table_data = [[0, 1, 2],
 [3, 4, 5],
 [6, 7, 8],
 [9, 10, 11],
 [12, 13, 14],
 [15, 16, 17],
 [18, 19, 20],
 [21, 22, 23],
 [24, 25, 26],
 [27, 28, 29]]

    table.insert_row([1,2,3])

    show_info("We created an editable and zebra style table. Click on a cell to edit.")

    root.mainloop()


import logging
import sqlite3
from datetime import datetime
from pathlib import Path
from sqlite3 import Error, Connection
from typing import List, Optional, Any, Tuple, Dict

from app.model.image_data import ImageData
from app.model.workspace import Workspace

_log = logging.getLogger(__name__)

class Repository:
    __connection: Connection = None

    def __init__(self, db_file: Path):
        try:
            self.__connection = sqlite3.connect(db_file)
            _log.debug(f"Using sqlite version {sqlite3.version}")
            self._ensure_schema()
        except Error as e:
            _log.error("Couldn't connect to the database", exc_info=e)
            raise e

    def __del__(self):
        if self.__connection is not None:
            self.__connection.close()
            del self.__connection

    def _ensure_schema(self):
        _log.debug("Ensuring schema is initialized")
        cur = self.__connection.cursor()
        cur.execute("PRAGMA foreign_keys = ON;")
        try:
            cur.executescript(SQL_CREATE_WORKSPACE_TABLE)
            cur.executescript(SQL_CREATE_IMAGE_TABLE)
            cur.connection.commit()
        finally:
            cur.close()

    @staticmethod
    def _dataclass_to_upsert_query(table_name: str, obj: Any) -> Tuple[str, Tuple[Any]]:
        # Convert the dataclass object to a dictionary
        workspace_dict = vars(obj)

        column_names = ', '.join(workspace_dict.keys())
        placeholders = ', '.join('?' * len(workspace_dict))
        query = f"INSERT OR REPLACE INTO {table_name} ({column_names}) VALUES ({placeholders})"
        return query, tuple(workspace_dict.values())

    # WORKSPACE #

    def get_all_workspaces(self) -> List[Workspace]:
        cur = self.__connection.cursor()
        cur.row_factory = Workspace.row_factory
        try:
            cur.execute("SELECT * FROM workspace ORDER BY last_used_at DESC, name ASC")
            return cur.fetchall()
        finally:
            cur.close()

    def get_workspace(self, id_pk: int) -> Optional[Workspace]:
        cur = self.__connection.cursor()
        cur.row_factory = Workspace.row_factory
        try:
            cur.execute("SELECT * FROM workspace WHERE id=?", (id_pk,))
            return cur.fetchone()
        finally:
            cur.close()

    def persist_workspace(self, obj: Workspace) -> Workspace:
        cur = self.__connection.cursor()
        cur.row_factory = Workspace.row_factory
        try:
            query, values = self._dataclass_to_upsert_query("workspace", obj)
            cur.execute(query, values)
            cur.connection.commit()
            # Retrieve the just inserted record
            cur.execute("SELECT * FROM workspace WHERE rowid=?", (cur.lastrowid,))
            return cur.fetchone()
        finally:
            cur.close()

    def rm_workspace(self, id_pk: int):
        cur = self.__connection.cursor()
        try:
            cur.execute("DELETE FROM workspace WHERE id=?", (id_pk,))
            cur.connection.commit()
        finally:
            cur.close()

    # IMAGE DATA #

    def get_all_images_for_workspace(self, workspace_id: int) -> List[ImageData]:
        cur = self.__connection.cursor()
        cur.row_factory = ImageData.row_factory
        try:
            # * performance optimization opportunity: don't load thumbs when not needed
            query = "SELECT * FROM image_data WHERE workspace_id=? ORDER BY workspace_id ASC, path ASC"
            cur.execute(query, (workspace_id,))
            return cur.fetchall()
        finally:
            cur.close()

    def get_image(self, workspace_id: int, path: str) -> Optional[ImageData]:
        cur = self.__connection.cursor()
        cur.row_factory = ImageData.row_factory
        try:
            cur.execute("SELECT * FROM image_data WHERE workspace_id=? AND path=?", (workspace_id, path))
            return cur.fetchone()
        finally:
            cur.close()

    def is_image_outdated(self, workspace_id: int, path: str, last_updated_at: datetime) -> bool:
        cur = self.__connection.cursor()
        cur.row_factory = ImageData.row_factory
        try:
            query = "SELECT * FROM image_data WHERE workspace_id=? AND path=? AND last_updated_at >= ?"
            cur.execute(query, (workspace_id, path, last_updated_at))
            return cur.fetchone() is None
        finally:
            cur.close()

    def persist_image(self, obj: ImageData) -> ImageData:
        cur = self.__connection.cursor()
        cur.row_factory = ImageData.row_factory
        try:
            query, values = self._dataclass_to_upsert_query("image_data", obj)
            cur.execute(query, values)
            cur.connection.commit()
            # Retrieve the just inserted record
            cur.execute("SELECT * FROM image_data WHERE rowid=?", (cur.lastrowid,))
            return cur.fetchone()
        finally:
            cur.close()

    def rm_image(self, workspace_id: int, path: str):
        cur = self.__connection.cursor()
        try:
            cur.execute("DELETE FROM image_data WHERE workspace_id=? AND path=?", (workspace_id, path))
            cur.connection.commit()
        finally:
            cur.close()

    def get_image_rank_histogram(self, workspace_id: int) -> Dict[int, int]:
        """
        Returns counts of images in every rank in the workspace (key is rank, value is count).
        """
        cur = self.__connection.cursor()
        try:
            cur.execute(
                "SELECT rank, COUNT(*) as count FROM image_data WHERE workspace_id=? GROUP BY rank ORDER BY rank ASC",
                (workspace_id,),
            )
            return dict(cur.fetchall())
        finally:
            cur.close()

SQL_CREATE_WORKSPACE_TABLE = """
CREATE TABLE IF NOT EXISTS workspace (
    id              integer PRIMARY KEY AUTOINCREMENT,
    name            text    NOT NULL,
    path            text    NOT NULL,
    last_used_at    text
);

CREATE UNIQUE INDEX IF NOT EXISTS idx_workspace_name
ON workspace(name);
CREATE INDEX IF NOT EXISTS        idx_workspace_last_used_at
ON workspace(last_used_at);
"""

SQL_CREATE_IMAGE_TABLE = """
CREATE TABLE IF NOT EXISTS image_data (
    workspace_id    integer NOT NULL,
    path            text    NOT NULL,

    size            integer NOT NULL,
    last_updated_at text    NOT NULL,
    width           integer NOT NULL,
    height          integer NOT NULL,
    thumbnail       blob    NULL,
    rank            integer DEFAULT 0 NOT NULL,

    PRIMARY KEY     (workspace_id, path),
    CONSTRAINT      fk_workspace
        FOREIGN KEY (workspace_id)
        REFERENCES  workspace(id)
        ON DELETE CASCADE
);

CREATE INDEX IF NOT EXISTS        idx_image_data_path
ON image_data(path);
CREATE INDEX IF NOT EXISTS        idx_image_data_last_updated_at
ON image_data(last_updated_at);
CREATE INDEX IF NOT EXISTS        idx_image_data_rank
ON image_data(rank);
"""


from modules.PickUpUrl import PickUpUrlClass
from modules.SaveAsPDF import SaveAsPDFClass
import sys
sys.dont_write_bytecode = True
try:
    pick_up_url = PickUpUrlClass()
    pick_up_url.read_cards()
    pick_up_url.serch_cards()
    pick_up_url.show_nohit_cards()
except:
    print("URLを読み込む過程でエラーが発生し、すべてのカードが検索できませんでした。")
    print("最後に検索したカード：" + pick_up_url.url_list[-1])

try:
    save_as_PDF = SaveAsPDFClass(pick_up_url.url_list)
    save_as_PDF.make_images()
except:
    print("ブラウザを動かせませんでした")
    print("取得したurl:")
    print(pick_up_url.url_list)


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

from odoo import models, fields, api, _
from odoo.tools import UserError

from datetime import datetime, timedelta

class HrEmployeeSch(models.Model):
    _inherit = 'hr.employee'

    schedule_ids = fields.One2many('specialist.schedule', 'specialist_id', 'Specialist Schedule Planning')

class Schedules(models.Model):
    _name = 'specialist.schedule'
    _description = 'Specialist Schedule'

    specialist_id = fields.Many2one('hr.employee', string="Especialista", required=True)
    start_date = fields.Datetime('Fecha y Hora de Inicio')
    date_delay = fields.Float('Duración')

    @api.constrains('date_delay')
    def range_duration(self):
        for rec in self:
            if rec.date_delay < 4 or rec.date_delay > 8:
                raise UserError(_("La duración del horario no puede ser menor de 4 o mayor de 8 horas."))

    @api.model_create_multi
    def create(self, vals):
        records = super(Schedules, self).create(vals)
        for r in records:
            fmt = "%Y-%m-%d %H:%M:%S"
            date_end = (datetime.strptime(str(r.start_date), fmt) + timedelta(hours=r.date_delay))
            vdate = datetime.strptime(str(r.start_date), fmt).date()
            vals_list = {
                'appoint_employ_id': r.specialist_id.id,
                'date_start': r.start_date,
                'duration': r.date_delay,
                'date_end': date_end,
                'appoint_date': vdate,
                'sch_id': r.id,
            }
            self.env['cits.admin'].create(vals_list)
        return records

    @api.multi
    def write(self, vals):
        record = super(Schedules, self).write(vals)
        fmt = "%Y-%m-%d %H:%M:%S"
        date_end = (datetime.strptime(str(self.start_date), fmt) + timedelta(hours=self.date_delay))
        vdate = datetime.strptime(str(self.start_date), fmt).date()
        vals_list = {
            'date_start': self.start_date,
            'duration': self.date_delay,
            'date_end': date_end,
            'appoint_date': vdate,
        }
        self.env['cits.admin'].sudo().search([('sch_id', '=', self.id)]).write(vals_list)
        return record

    @api.multi
    def unlink(self):
        res = super(Schedules, self).unlink()
        self.env['cits.admin'].sudo().search([('sch_id', '=', self.id)]).unlink()
        return res


# -*- coding: utf-8 -*-
# @Time    : 2020/3/6 15:35
# @Author  : Deng Wenxing
# @Email   : dengwenxingae86@163.com
# @File    : call_msg.py
# @Software: PyCharm
# @content : 节点结构信息，关系依赖信息

from collections import OrderedDict

def get_table_index(tablename):
    table_index = {
        'vertex_person': 0,	# 人 40亿空间
        'vertex_imsi': 4000000000,	# IMSI 20亿
        'vertex_imei': 5000000000,	# IMEI 10亿
        'vertex_autolpn': 6000000000,	# 车牌号码' 10亿
        'vertex_vehicle': 7000000000,	# 机车（车架号）' 10亿
        'vertex_case': 8000000000,	# 案件' 10亿
        'vertex_jail': 9000000000,	# 所' 10亿
        'vertex_hukou': 10000000000,	# 户口'  10亿
        'vertex_internetbar': 11000000000,	# 网吧' 10亿
        'vertex_ip': 12000000000,	# IP地址' 10亿
        'vertex_hotel': 13000000000,	# 宾馆' 10亿
        'vertex_airline': 14000000000,	# 航班班次' 10亿
        'vertex_ssaccount': 15000000000,	# 社保账号' 10亿
        'vertex_trainline': 17000000000,	# 火车班次' 10亿
        'vertex_package': 21000000000,	# 快递(快递单号）' 10亿
        'vertex_certificate': 25000000000,	# 证件' 10亿
        'vertex_school': 27000000000,	# 学校' 10亿
        'vertex_college': 28000000000,	# 学院' 10亿
        'vertex_company': 29000000000,	# 公司' 10亿
        'vertex_vbn': 30000000000,	# 宽带账号' 10亿
        'vertex_phonenumber': 31000000000,	# 电话 20亿空间
    }
    return table_index[tablename]

# 所有表字段配置
vertex_table_info = OrderedDict()
vertex_table_info['vertex_person'] = ['zjhm', 'zjlx', 'gj', 'xm', 'ywxm', 'zym', 'xb', 'csrq', 'mz', 'jg',
                                      'whcd', 'hyzk', 'zzmm', 'hkszdxz', 'sjjzxz']
vertex_table_info['vertex_phonenumber'] = ['phone', 'xm','province', 'city']
vertex_table_info['vertex_imsi'] = ['imsi']
vertex_table_info['vertex_imei'] = ['imei']
vertex_table_info['vertex_autolpn'] = ['autolpn','hpzl']
vertex_table_info['vertex_vehicle'] = ['vehicle', 'fdjh', 'cllx', 'csys']
vertex_table_info['vertex_case'] = ['asjbh', 'ajmc', 'asjfskssj', 'asjfsjssj', 'asjly', 'ajlb', 'fxasjsj',
                                    'fxasjdd_dzmc', 'jyaq', 'asjswry_rs', 'asjssry_rs', 'ssjzrmby', 'slsj',
                                    'sldw_gajgjgdm', 'sldw_gajgmc', 'larq']
vertex_table_info['vertex_jail'] = ['unitid', 'unitname', 'unitname_s', 'unitkindid', 'dwdz']
vertex_table_info['vertex_hukou'] = ['hukouid', 'hukou']
vertex_table_info['vertex_internetbar'] = ['siteid', 'regtime', 'serial', 'sitetype', 'businessstate', 'title',
                                           'address', 'sp', 'servercount', 'terminalcount', 'nettype', 'employeecount',
                                           'ownercertnumber']
vertex_table_info['vertex_ip'] = ['ip', 'tablename']
vertex_table_info['vertex_hotel'] = ['lgdm', 'qiyemc', 'yyzz', 'xxdz']
vertex_table_info['vertex_airline'] = ['airlineid', 'hbh', 'hbrq', 'yjqfsj', 'yjddsj', 'sfd', 'mdd']
vertex_table_info['vertex_ssaccount'] = ['sbkh', 'tablename']
vertex_table_info['vertex_trainline'] = ['trianid', 'cc', 'fcrq']
vertex_table_info['vertex_email'] = ['email', 'nick', 'tablename']
vertex_table_info['vertex_package'] = ['packageid', 'unitcode', 'orderid', 'accepttime', 'inputtime', 'realarrivetime',
                                       'acceptoperatorphone', 'sendoperatorphone', 'sendername', 'sendercardid',
                                       'senderpostcode', 'senderphone', 'sendernation', 'sendercountry',
                                       'senderaddress', 'receivername', 'receivercardid', 'receiverpostcode',
                                       'receiverphone', 'receivernation', 'receivercountry', 'receiveraddress',
                                       'amountreceivable', 'currency', 'itemname', 'itemquantity', 'totalweight',
                                       'totalsize', 'insurancevalue', 'itemremark', 'senderlongitude', 'senderlatitude',
                                       'receiverlongitude', 'receiverlatitude', 'tablename']
vertex_table_info['vertex_qq'] = ['account', 'tablename']
vertex_table_info['vertex_wechat'] = ['account', 'tablename']
vertex_table_info['vertex_certificate'] = ['certificateid', 'zjhm', 'zjlx', 'zjname', 'dz']
vertex_table_info['vertex_school'] = ['school_id', 'name', 'history_name','school_address']
vertex_table_info['vertex_company'] = ['company', 'dwmc', 'dwdz', 'zzjgdm', 'tyshdm', 'clrq', 'jyzt', 'jyfw',
                                       'dwlx', 'zczb', 'cyrs', 'hylb', 'dwscdz', 'bgxq', 'bgrq']

# 边字段配置
edge_table_info = OrderedDict()
edge_table_info['edge_person_reserve_airline'] = ['sfzh', 'airlineid', 'hbh', 'hbrq', 'sfd', 'mdd']
edge_table_info['edge_person_spouse_person'] = ['sfzhnan', 'sfzhnv', 'start_time', 'end_time']
edge_table_info['edge_person_checkin_train'] = ['sfzh', 'tgkkdm', 'tgkkmc', 'tgsj', 'tablename']
edge_table_info['edge_certificate_open_phone'] = ['certificateid', 'phone', 'start_time', 'end_time', 'is_xiaohu',
                                                  'tablename']
edge_table_info['edge_person_has_ssaccount'] = ['sfzh', 'sbkh', 'link_time', 'tablename']
edge_table_info['edge_find_common_greatgrand'] = ['sfzh1', 'sfzh2']
edge_table_info['edge_find_spouse'] = ['sfzhnan', 'sfzhnnv']
edge_table_info['edge_person_work_school'] = ['start_person', 'end_school_id', 'start_time', 'end_time', 'jszc',
                                              'cjgzsj', 'gzxz', 'sjkm', 'tablename']
edge_table_info['edge_find_father'] = ['sfzh', 'fqsfzh']
edge_table_info['edge_find_grandparents'] = ['sfzh', 'grandsfzh']
edge_table_info['edge_find_greatgrands'] = ['sfzh', 'greatgrandsfzh']
edge_table_info['edge_find_mother'] = ['sfzh', 'mqsfzh']
edge_table_info['edge_person_attend_college'] = ['start_person', 'end_college_id', 'start_time', 'end_time', 'zymc',
                                                 'xuehao', 'xuezhi', 'tablename']
edge_table_info['edge_person_fatherIs_person'] = ['sfzh', 'fqsfzh', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_case_SuspectIs_person'] = ['asjbh', 'sfzh', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_certificate_link_phone'] = ['certificateid', 'phone', 'start_time', 'end_time', 'num',
                                                  'tablename']
edge_table_info['edge_vehicle_sameguarantee'] = ['start_person', 'end_person']
edge_table_info['edge_person_stay_hotel'] = ['sfzh', 'lgdm', 'start_time', 'end_time', 'num']
edge_table_info['edge_internetbar_use_ip'] = ['siteid', 'ip', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_person_reserve_trainline'] = ['sfzh', 'trianid', 'cc', 'spsj', 'fcrq', 'zwh', 'fz', 'dz',
                                                    'tablename']
edge_table_info['edge_wechat_msg_wechat'] = ['start_account', 'end_account', 'start_time', 'end_time', 'num',
                                             'tablename']
edge_table_info['edge_person_arrived_airline'] = ['sfzh', 'airlineid', 'hbh', 'cwdj', 'zwh', 'hbrq', 'yjddsj', 'sfd',
                                                  'mdd', 'tablename']
edge_table_info['edge_phone_send_package'] = ['phone', 'packageid', 'send_time', 'tablename']
edge_table_info['edge_person_guardianIs_person'] = ['sfzh', 'jhrsfzh', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_person_guaranteeis_person'] = ['start_person', 'end_person', 'start_time', 'end_time',
                                                     'tablename']
edge_table_info['edge_find_common_grand'] = ['sfzh1', 'sfzh2']
edge_table_info['edge_vehicle_has_autolpn'] = ['start_vehicle', 'end_autolpn', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_email_use_phone'] = ['email', 'phone', 'start_time', 'end_time', 'num', 'tablename']
edge_table_info['edge_person_hire_house'] = ['start_person', 'end_house_id', 'fwdz', 'start_time', 'end_time', 'htbh',
                                             'xm', 'hx', 'fwlx', 'phone', 'mph', 'tablename']
edge_table_info['edge_person_maintain_vehicle'] = ['start_person', 'end_vehicle', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_person_link_hukou'] = ['sfzh', 'hukouid', 'start_time', 'end_time', 'isfl', 'yhzgx', 'tablename']
edge_table_info['edge_person_surfing_internetbar'] = ['sfzh', 'siteid', 'start_time', 'end_time', 'num', 'tablename']
edge_table_info['edge_phone_list_phone'] = ['start_phone', 'end_phone', 'start_time', 'end_time', 'relationship_name',
                                            'relationship_email', 'relationship_address', 'tablename']
edge_table_info['edge_person_attend_school'] = ['start_person', 'end_school_id', 'start_time', 'end_time', 'fymc', 'bj',
                                                'zymc', 'xuezhi', 'tablename']
edge_table_info['edge_person_own_autolpn'] = ['start_person', 'end_autolpn', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_person_smz_phone'] = ['start_person', 'end_phone', 'start_time', 'end_time', 'xm', 'cnt',
                                            'matches', 'match_src', 'time_rn', 'matches_rn', 'cert_src', 'cert_weigth']
edge_table_info['edge_case_reportby_person'] = ['asjbh', 'sfzh', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_person_motherIs_person'] = ['sfzh', 'mqsfzh', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_case_suspicious_person'] = ['asjbh', 'sfzh', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_person_stay_jail_qjs'] = ['sfzh', 'jsbh', 'sylx', 'rsyy', 'start_time', 'end_time', 'fjh',
                                                'tablename']
edge_table_info['edge_person_stay_jail_jls'] = ['sfzh', 'jsbh', 'sylx', 'rsyy', 'start_time', 'end_time', 'fjh',
                                                'rsgjmjid', 'csgjmjid', 'tablename']
edge_table_info['edge_person_open_phone'] = ['sfzh', 'phone', 'start_time', 'end_time', 'is_xiaohu', 'tablename']
edge_table_info['edge_qq_msg_qq'] = ['start_account', 'end_account', 'start_time', 'end_time', 'num', 'tablename']
edge_table_info['edge_vehicle_sameintroducer'] = ['start_person', 'end_person']
edge_table_info['edge_qq_use_phone'] = ['account', 'phone', 'start_time', 'end_time', 'num', 'tablename']
edge_table_info['edge_person_work_com'] = ['start_person', 'end_company', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_person_visit_person'] = ['tfrsfzh', 'sfzh', 'start_time', 'end_time', 'num', 'tablename']
edge_table_info['edge_person_reserve_coachline'] = ['sfzh', 'coachid', 'spsj', 'zwh', 'gpczmc', 'tablename']
edge_table_info['edge_person_principal_com'] = ['start_person', 'end_company', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_person_introduceris_person'] = ['start_person', 'end_person', 'start_time', 'end_time',
                                                      'tablename']
edge_table_info['edge_wechat_use_phone'] = ['account', 'phone', 'start_time', 'end_time', 'num', 'tablename']
edge_table_info['edge_find_common_mother'] = ['sfzh1', 'sfzh2']
edge_table_info['edge_same_kss'] = ['sfzh1', 'sfzh2', 'data', 'num', 'start_time', 'end_time']
edge_table_info['edge_same_qjs'] = ['sfzh1', 'sfzh2', 'data', 'num', 'start_time', 'end_time']
edge_table_info['edge_person_buy_vehicle'] = ['start_person', 'end_vehicle', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_person_invest_com'] = ['start_person', 'end_company', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_person_link_phone'] = ['sfzh', 'phone', 'start_time', 'end_time', 'linknum', 'tablename']
edge_table_info['edge_phonenumber_use_imei'] = ['phone', 'imei', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_school_sub_college'] = ['start_school_id', 'end_college_id', 'tablename']
edge_table_info['edge_person_hire_autolpn'] = ['start_person', 'end_autolpn', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_phonenumber_use_imsi'] = ['phone', 'imsi', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_phone_sendpackage_phone'] = ['start_phone', 'end_phone', 'start_time', 'end_time', 'num',
                                                   'tablename']
edge_table_info['edge_person_checkin_airline'] = ['sfzh', 'airlineid', 'hbh', 'cwdj', 'zwh', 'hbrq', 'zjsj', 'djk',
                                                  'sfd', 'mdd', 'tablename']
edge_table_info['edge_person_stay_jail_kss'] = ['sfzh', 'jsbh', 'sylx', 'sxzm', 'start_time', 'end_time', 'fjh',
                                                'tablename']
edge_table_info['edge_certificate_link_person'] = ['certificateid', 'sfzh', 'start_time', 'end_time', 'num',
                                                   'tablename']
edge_table_info['edge_person_link_drivinglicense'] = ['sfzh', 'drivinglicense', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_person_has_nchca'] = ['sfzh', 'nchca', 'link_time', 'tablename']
edge_table_info['edge_find_common_father'] = ['sfzh1', 'sfzh2']
edge_table_info['edge_phone_receive_package'] = ['phone', 'packageid', 'receive_time', 'tablename']
edge_table_info['edge_find_common_guardian'] = ['sfzh1', 'sfzh2']
edge_table_info['edge_person_legal_com'] = ['start_person', 'end_company', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_person_work_com2'] = ['start_person', 'end_company', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_same_kss_house'] = ['sfzh1', 'sfzh2', 'data', 'num', 'start_time', 'end_time']
edge_table_info['edge_same_qjs_house'] = ['sfzh1', 'sfzh2', 'data', 'num', 'start_time', 'end_time']
edge_table_info['edge_email_send_email'] = ['start_email', 'end_email', 'start_time', 'end_time', 'num', 'tablename']
edge_table_info['edge_case_VictimIs_person'] = ['asjbh', 'sfzh', 'start_time', 'end_time', 'tablename']
edge_table_info['edge_person_open_vbn'] = ['sfzh', 'kdzh', 'start_time', 'end_time', 'is_xiaohu', 'khxm', 'azdz',
                                           'tablename']
edge_table_info['edge_house_hiremate'] = ['start_person', 'end_person', 'fwdz', 'start_time', 'end_time']
edge_table_info['edge_company_workmate'] = ['start_person', 'end_person', 'start_time', 'end_time', 'dwmc']
edge_table_info['edge_find_common_hukou'] = ['sfzh1', 'sfzh2', 'hukou']
edge_table_info['edge_groupmsg'] = ['start_phone', 'end_phone', 'start_time', 'end_time', 'message_number']
edge_table_info['edge_groupcall'] = ['start_phone', 'end_phone', 'start_time', 'end_time', 'call_total_duration',
                                     'call_total_times']
edge_table_info['edge_same_hotel_house'] = ['sfzh1', 'sfzh2', 'num']
edge_table_info['edge_same_hotel'] = ['sfzh1', 'sfzh2', 'num']
edge_table_info['edge_schoolmate'] = ['start_person', 'end_person', 'start_time', 'end_time', 'name']
edge_table_info['edge_phone_sameimei'] = ['start_phone', 'end_phone', 'start_time', 'end_time', 'times']
edge_table_info['edge_person_with_coach_travel'] = ['sfzh1', 'sfzh2', 'start_time', 'end_time', 'num']
edge_table_info['edge_person_with_airline_travel'] = ['sfzh1', 'sfzh2', 'start_time', 'end_time', 'num']
edge_table_info['edge_person_with_trainline_travel'] = ['sfzh1', 'sfzh2', 'start_time', 'end_time', 'num']
edge_table_info['edge_person_with_internetbar_surfing'] = ['sfzh1', 'sfzh2', 'start_time', 'end_time', 'num']
edge_table_info['edge_phone_wechat_phone'] = ['start_phone', 'end_phone', 'account', 'start_time', 'end_time']
edge_table_info['edge_phone_qq_phone'] = ['start_phone', 'end_phone', 'account', 'start_time', 'end_time']
edge_table_info['edge_phone_email_phone'] = ['start_phone', 'end_phone', 'account', 'start_time', 'end_time']
edge_table_info['edge_person_with_car'] = ['start_person', 'end_autolpn', 'count', 'fullcount']
edge_table_info['relation_score_res'] = ['sfzh1', 'sfzh2', 'data', 'last_tb_score']

# 所有边关系配置
# key为边表，值依次为，start_id 的依赖表，end_id 的依赖表，start_id 的依赖表，end_id 的依赖表字段
edge_info = {'edge_person_reserve_airline': ['vertex_person_jg', 'zjhm', 'vertex_airline_jg', 'airlineid'],
    'edge_person_spouse_person': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_person_checkin_train': ['vertex_person_jg', 'zjhm', 'vertex_traincheckin_jg', 'tgkkdm'],
    'edge_certificate_open_phone': ['vertex_certificate_jg', 'certificateid', 'vertex_phonenumber_jg', 'phone'],
    'edge_person_has_ssaccount': ['vertex_person_jg', 'zjhm', 'vertex_ssaccount_jg', 'sbkh'],
    'edge_find_common_greatgrand': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_groupcall': ['vertex_phonenumber_jg', 'phone', 'vertex_phonenumber_jg', 'phone'],
    'edge_find_spouse': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_person_work_school': ['vertex_person_jg', 'zjhm', 'vertex_school_jg', 'school_id'],
    'edge_find_father': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_find_mother': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_find_grandparents': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_find_greatgrands': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_person_attend_college': ['vertex_person_jg', 'zjhm', 'vertex_college_jg', 'college_id'],
    'edge_person_fatherIs_person': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_case_SuspectIs_person': ['vertex_case_jg', 'asjbh', 'vertex_person_jg', 'zjhm'],
    'edge_certificate_link_phone': ['vertex_certificate_jg', 'certificateid', 'vertex_phonenumber_jg', 'phone'],
    'edge_vehicle_sameguarantee': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_person_stay_hotel': ['vertex_person_jg', 'zjhm', 'vertex_hotel_jg', 'lgdm'],
    'edge_internetbar_use_ip': ['vertex_internetbar_jg', 'siteid', 'vertex_ip_jg', 'ip'],
    'edge_person_reserve_trainline': ['vertex_person_jg', 'zjhm', 'vertex_trainline_jg', 'trianid'],
    'edge_wechat_msg_wechat': ['vertex_wechat_jg', 'account', 'vertex_wechat_jg', 'account'],
    'edge_person_arrived_airline': ['vertex_person_jg', 'zjhm', 'vertex_airline_jg', 'airlineid'],
    'edge_phone_send_package': ['vertex_phonenumber_jg', 'phone', 'vertex_package_jg', 'packageid'],
    'edge_person_guardianIs_person': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_person_guaranteeis_person': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_find_common_grand': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_vehicle_has_autolpn': ['vertex_vehicle_jg', 'vehicle', 'vertex_autolpn_jg', 'autolpn'],
    'edge_email_use_phone': ['vertex_email_jg', 'email', 'vertex_phonenumber_jg', 'phone'],
    'edge_person_hire_house': ['vertex_person_jg', 'zjhm', 'vertex_house_jg', 'house_id'],
    'edge_person_maintain_vehicle': ['vertex_person_jg', 'zjhm', 'vertex_vehicle_jg', 'vehicle'],
    'edge_person_link_hukou': ['vertex_person_jg', 'zjhm', 'vertex_hukou_jg', 'hukouid'],
    'edge_person_surfing_internetbar': ['vertex_person_jg', 'zjhm', 'vertex_internetbar_jg', 'siteid'],
    'edge_phone_list_phone': ['vertex_phonenumber_jg', 'phone', 'vertex_phonenumber_jg', 'phone'],
    'edge_person_attend_school': ['vertex_person_jg', 'zjhm', 'vertex_school_jg', 'school_id'],
    'edge_person_own_autolpn': ['vertex_person_jg', 'zjhm', 'vertex_autolpn_jg', 'autolpn'],
    'edge_person_stay_jail_jls': ['vertex_person_jg', 'zjhm', 'vertex_jail_jg', 'unitid'],
    'edge_person_smz_phone': ['vertex_person_jg', 'zjhm', 'vertex_phonenumber_jg', 'phone'],
    'edge_case_reportby_person': ['vertex_case_jg', 'asjbh', 'vertex_person_jg', 'zjhm'],
    'edge_person_motherIs_person': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_case_suspicious_person': ['vertex_case_jg', 'asjbh', 'vertex_person_jg', 'zjhm'],
    'edge_person_stay_jail_qjs': ['vertex_person_jg', 'zjhm', 'vertex_jail_jg', 'unitid'],
    'edge_person_open_phone': ['vertex_person_jg', 'zjhm', 'vertex_phonenumber_jg', 'phone'],
    'edge_qq_msg_qq': ['vertex_qq_jg', 'account', 'vertex_qq_jg', 'account'],
    'edge_vehicle_sameintroducer': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_qq_use_phone': ['vertex_qq_jg', 'account', 'vertex_phonenumber_jg', 'phone'],
    'edge_person_work_com': ['vertex_person_jg', 'zjhm', 'vertex_company_jg', 'company'],
    'edge_person_visit_person': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_person_reserve_coachline': ['vertex_person_jg', 'zjhm', 'vertex_coachline_jg', 'coachid'],
    'edge_person_principal_com': ['vertex_person_jg', 'zjhm', 'vertex_company_jg', 'company'],
    'edge_person_introduceris_person': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_same_hotel_house': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_same_hotel': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_groupmsg': ['vertex_phonenumber_jg', 'phone', 'vertex_phonenumber_jg', 'phone'],
    'edge_wechat_use_phone': ['vertex_wechat_jg', 'account', 'vertex_phonenumber_jg', 'phone'],
    'edge_company_workmate': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_find_common_mother': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_same_kss': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_same_qjs': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_person_buy_vehicle': ['vertex_person_jg', 'zjhm', 'vertex_vehicle_jg', 'vehicle'],
    'edge_person_invest_com': ['vertex_person_jg', 'zjhm', 'vertex_company_jg', 'company'],
    'edge_house_hiremate': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_person_link_phone': ['vertex_person_jg', 'zjhm', 'vertex_phonenumber_jg', 'phone'],
    'edge_phonenumber_use_imei': ['vertex_phonenumber_jg', 'phone', 'vertex_imei_jg', 'imei'],
    'edge_school_sub_college': ['vertex_school_jg', 'school_id', 'vertex_college_jg', 'college_id'],
    'edge_person_hire_autolpn': ['vertex_person_jg', 'zjhm', 'vertex_autolpn_jg', 'autolpn'],
    'edge_phonenumber_use_imsi': ['vertex_phonenumber_jg', 'phone', 'vertex_imsi_jg', 'imsi'],
    'edge_phone_sendpackage_phone': ['vertex_phonenumber_jg', 'phone', 'vertex_phonenumber_jg', 'phone'],
    'edge_person_checkin_airline': ['vertex_person_jg', 'zjhm', 'vertex_airline_jg', 'airlineid'],
    'edge_person_stay_jail_kss': ['vertex_person_jg', 'zjhm', 'vertex_jail_jg', 'unitid'],
    'edge_certificate_link_person': ['vertex_certificate_jg', 'certificateid', 'vertex_person_jg', 'zjhm'],
    'edge_person_link_drivinglicense': ['vertex_person_jg', 'zjhm', 'vertex_drivinglicense_jg', 'drivinglicense'],
    'edge_person_has_nchca': ['vertex_person_jg', 'zjhm', 'vertex_nchcAccount_jg', 'nchca'],
    'edge_find_common_father': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_phone_receive_package': ['vertex_phonenumber_jg', 'phone', 'vertex_package_jg', 'packageid'],
    'edge_find_common_guardian': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_person_legal_com': ['vertex_person_jg', 'zjhm', 'vertex_company_jg', 'company'],
    'edge_person_work_com2': ['vertex_person_jg', 'zjhm', 'vertex_company_jg', 'company'],
    'edge_same_kss_house': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_same_qjs_house': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_email_send_email': ['vertex_email_jg', 'email', 'vertex_email_jg', 'email'],
    'edge_case_VictimIs_person': ['vertex_case_jg', 'asjbh', 'vertex_person_jg', 'zjhm'],
    'edge_person_open_vbn': ['vertex_person_jg', 'zjhm', 'vertex_vbn_jg', 'kdzh'],
    'edge_find_common_hukou': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_phone_sameimei': ['vertex_phonenumber_jg', 'phone', 'vertex_phonenumber_jg', 'phone'],
    'edge_schoolmate': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_person_with_coach_travel': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_person_with_airline_travel': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_person_with_trainline_travel': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_person_with_internetbar_surfing': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'],
    'edge_phone_wechat_phone': ['vertex_phonenumber_jg', 'phone', 'vertex_phonenumber_jg', 'phone'],
    'edge_phone_qq_phone': ['vertex_phonenumber_jg', 'phone', 'vertex_phonenumber_jg', 'phone'],
    'edge_phone_email_phone': ['vertex_phonenumber_jg', 'phone', 'vertex_phonenumber_jg', 'phone'],
    'edge_person_with_car': ['vertex_person_jg', 'zjhm', 'vertex_autolpn_jg', 'autolpn'],
    'relation_score_res': ['vertex_person_jg', 'zjhm', 'vertex_person_jg', 'zjhm'], }


import datetime
from PyQt5.QtWidgets import *
from PyQt5.QtChart import *
from overview import *
from PyQt5 import uic
from PyQt5.QtCore import *
from pyupbit import Upbit
#from volatility import *

class worker(QThread):
    def __init__(self, b):
        super().__init__()
        self.b = b

    def end(self):
        self.alive = False

    def run(self):
        hold_flag = False
        self.alive = True
        #target_price = get_target_price()

        while self.alive:
            now = datetime.datetime.now()
            mid = datetime.datetime(now.year, now.month, now.day)
            delta = datetime.timedelta(seconds=10)

class MyWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        uic.loadUi("main.ui", self)

        with open("upbit.txt", "r") as f:
            key0 = f.readline().strip()
            key1 = f.readline().strip()

        self.key0.setText(key0)
        self.key1.setText(key1)

        self.btn.setText("login")
        self.btn.clicked.connect(self.login)

    #이부분 수정 필요함
    def login(self):
        if self.btn.text() == "login":
            b = Upbit(self.key0.text(), self.key1.text())
            balance = b.get_balance("KRW")
            self.log.append(f"[로그인] 성공 / 현금 : {balance}")
            self.btn.setText("logout")

            #self.w = Worker(b)
            #self.w.start()

        else:
            # 자동매매 로직 종료
            #self.w.end()
            self.btn.setText("login")
            self.log.append(f"[로그아웃] 성공 / 자동매매종료")

    def closeEvent(self, event):
        self.overviewWidget.closeEvent(event)
        self.chartWidget.closeEvent(event)
        self.orderbookWidget.closeEvent(event)

if __name__ == "__main__":
    app = QApplication([])
    m  = MyWindow()
    m.show()
    app.exec_()


nm = input().split()
n = int(nm[0])
m = int(nm[1])
arr = []

for _ in range(n):
    arr.append(list(map(int, input().rstrip().split())))

k = int(input())

array = sorted(arr, key=lambda arr_entry: arr_entry[k])

for i in range(n):
    for j in range(m):
        print(array[i][j],end=' ')
    print(' ')


import os
import argparse

def extract_static_table():
    path = os.path.join(
        os.path.dirname(os.path.realpath(__file__)),
        'static_table.txt'
    )

    with open(path, 'r') as f:
        print('(')

        lines = list(f)
        for line in lines[3:len(lines)-1]:
            sp = line.split('|')
            name = sp[2].strip()
            value = sp[3].strip()
            print("    ('{0}', '{1}'),".format(name, value))

        print(')\n')

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Extract data (as tuples) from *.txt tables of this repo.')
    parser.add_argument('-s', '--static', action='store_true', help='print static tuple')
    parser.add_argument('-H', '--huffman', action='store_true', help='print huffman tuple')

    args = parser.parse_args()
    if args.static:
        extract_static_table()
    if args.huffman:
        pass


import unittest
from datetime import date, datetime
from unittest import mock
from uuid import uuid4

from faker import Faker
from mock import MagicMock
from requests import Response

from ynab.api import YNAB, ImportIdGenerator, TransactionStore
from ynab.secrets import Keyring

faker = Faker()

class TestApi(unittest.TestCase):
    def test_import_id_generator(self):
        generator = ImportIdGenerator()
        ids = [
            generator.generate(
                date=datetime(year=2018, month=4, day=13, hour=3, minute=44),
                milliunit_amount=12345,
            ),
            generator.generate(
                date=datetime(year=2018, month=4, day=13, hour=3, minute=44),
                milliunit_amount=12345,
            ),
            generator.generate(
                date=datetime(year=2014, month=8, day=1, hour=16, minute=14),
                milliunit_amount=5432,
            ),
        ]
        expected = [
            "YNAB:12345:2018-04-13:1",
            "YNAB:12345:2018-04-13:2",
            "YNAB:5432:2014-08-01:1",
        ]
        self.assertEqual(ids, expected)

    def test_api(self):
        store = TransactionStore()
        store.append(
            transaction_date=date(year=2018, month=4, day=13),
            payee_name="a very long payee name that will be longer than 50 characters",
            memo=(
                "do not forget to say a very long string that should go over the "
                "limit of the number of characters you are allowed to enter in"
            ),
            amount=12.3456,
        )
        store.append(
            transaction_date=date(year=2018, month=4, day=13),
            payee_name="obama",
            memo="this memo is ok",
            amount=-120_000,
        )
        expected = {
            "transactions": [
                {
                    "account_id": "some-account-id",
                    "date": "2018-04-13",
                    "amount": 12346,
                    "payee_name": "a very long payee name that will be longer than 50",
                    "memo": (
                        "ry long string that should go over the limit of the number of "
                        "characters you are allowed to enter in"
                    ),
                    "import_id": "YNAB:12346:2018-04-13:1",
                    "cleared": "cleared",
                },
                {
                    "account_id": "some-account-id",
                    "date": "2018-04-13",
                    "amount": -120_000_000,
                    "payee_name": "obama",
                    "memo": "this memo is ok",
                    "import_id": "YNAB:-120000000:2018-04-13:1",
                    "cleared": "cleared",
                },
            ]
        }
        self.assertEqual(expected, store.json("some-account-id"))

    def test_get(self):
        account_id = str(uuid4())

        for payee_name, memo in [(faker.name(), faker.sentence()), (None, None)]:
            ynab_response = {
                "data": {
                    "transactions": [
                        {
                            "id": str(uuid4()),
                            "date": "2019-11-16",
                            "amount": -9500,
                            "memo": memo,
                            "cleared": "reconciled",
                            "approved": True,
                            "flag_color": None,
                            "account_id": account_id,
                            "account_name": faker.word(),
                            "payee_id": None,
                            "payee_name": payee_name,
                            "category_id": str(uuid4()),
                            "category_name": faker.word(),
                            "transfer_account_id": None,
                            "transfer_transaction_id": None,
                            "matched_transaction_id": None,
                            "import_id": "YNAB:-9500:2019-11-16:1",
                            "deleted": False,
                            "subtransactions": [],
                        },
                        # deleted transaction
                        {
                            "date": "2019-11-16",
                            "amount": -9501,
                            "memo": memo,
                            "payee_name": payee_name,
                            "import_id": "YNAB:-9501:2019-11-16:1",
                            "deleted": True,
                        },
                    ]
                }
            }

            mock_response = MagicMock(spec=Response)
            mock_response.json.return_value = ynab_response
            mock_keyring = MagicMock(spec=Keyring)
            with mock.patch.object(YNAB, "secret", return_value="some_secret"):
                with mock.patch.object(YNAB, "validate_secrets", return_value=True):
                    ynab = YNAB.from_config(
                        {"secrets_keys": {"access_token": ""}}, keyring=mock_keyring
                    )

                    with mock.patch("requests.get", return_value=mock_response):
                        transactions = ynab.get("account_id", "budget_id")
                        self.assertEqual(
                            transactions.count(), 1
                        )  # no deleted transaction
                        (t,) = transactions.transactions
                        self.assertEqual(t.date, date(2019, 11, 16))
                        self.assertEqual(t.payee_name, payee_name or "")
                        self.assertEqual(t.memo, memo or "")
                        self.assertEqual(t.milliunit_amount, -9500)