import ccxt
import time
import pandas as pd
import numpy as np
import datetime
from pprint import pprint
import pal
import TG_Pop_Up
from pybit import usdt_perpetual

pd.set_option('display.max_rows', None)
pd.set_option('display.max_columns', None)
pd.set_option('display.width', 1000)

###### API Zone ######
exchange = ccxt.bybit({
    'apiKey': pal.api_key,
    'secret': pal.api_secret,
})

markets = exchange.load_markets()

session_auth = usdt_perpetual.HTTP(
    endpoint="https://api.bybit.com",
    api_key=pal.api_key,
    api_secret=pal.api_secret
)

# print('********************************')
symbol = 'APEUSDT'
market = exchange.market(symbol)

###### All Functions ######
def open_long():
    session_auth.place_active_order(
        symbol="APEUSDT",
        side="Buy",
        order_type="Market",
        qty=bet_size,
        # price=5,
        time_in_force="GoodTillCancel",
        reduce_only=False,
        close_on_trigger=False,
        position_idx=0
    )

def settle_long():
    session_auth.place_active_order(
        symbol="APEUSDT",
        side="Sell",
        order_type="Market",
        qty=bet_size,
        time_in_force="GoodTillCancel",
        reduce_only=True,
        close_on_trigger=False,
        position_idx=0
    )

def open_short():
    session_auth.place_active_order(
        symbol="APEUSDT",
        side="Sell",
        order_type="Market",
        qty=bet_size,
        # price=5,
        time_in_force="GoodTillCancel",
        reduce_only=False,
        close_on_trigger=False,
        position_idx=0
    )

def settle_short():
    session_auth.place_active_order(
        symbol="APEUSDT",
        side="Buy",
        order_type="Market",
        qty=bet_size,
        time_in_force="GoodTillCancel",
        reduce_only=True,
        close_on_trigger=False,
        position_idx=0
    )

def reverse_to_short():
    settle_long()
    time.sleep(1)
    open_short()

def reverse_to_long():
    settle_short()
    time.sleep(1)
    open_long()

### signal ###
def signal(df, x, y):

    df['ma'] = df['close'].rolling(x).mean()
    df['sd'] = df['close'].rolling(x).std()
    df['z'] = (df['close'] - df['ma']) / df['sd']

    df['pos'] = np.where(df['z'] > y, 1, np.where(df['z'] < -y, -1, 0))

    pos = df['pos'].iloc[-1] #read the last row

    # df['dt'] = pd.to_datetime(df['DateTime']/1000, unit='s')

    print(df.tail(10))

    return pos

###### trade ######
def trade(pos):

    ### get account info before trade ###
    net_pos = float(exchange.fetchPositions([symbol])[0]['info']['size'])
    net_pos_side = (exchange.fetchPositions([symbol])[0]['info']['side'])

    ### trade ###
    if pos == 1:
        ### Open Long ###
        if net_pos == 0:
            open_long()
            TG_Pop_Up.tg_pop_long()

        ### -1 to 1 ###
        elif net_pos == bet_size and net_pos_side == 'Sell':
            reverse_to_long()
            TG_Pop_Up.tg_pop_reverse_to_long()

    elif pos == 0:
        ### Settle Long ###
        if net_pos == bet_size and net_pos_side == 'Buy':
            settle_long()
            TG_Pop_Up.tg_pop_settle_long()
        ### Settle Short ###
        elif net_pos == bet_size and net_pos_side == 'Sell':
            settle_short()
            TG_Pop_Up.tg_pop_settle_short()

    elif pos == -1:
        ### Open Short ###
        if net_pos == 0:
            open_short()
            TG_Pop_Up.tg_pop_short()
        ### 1 to -1 ###
        if net_pos == bet_size and net_pos_side == 'Buy':
            reverse_to_short()
            TG_Pop_Up.tg_pop_reverse_to_short()

    time.sleep(1)

    ### get account info after trade ###
    net_pos = float(exchange.fetchPositions([symbol])[0]['info']['size'])
    print('after signal')
    print('net position', net_pos)
    print('nav', datetime.datetime.now(), exchange.fetch_balance()['USDT']['total'])

### param ###
x = 30
y = 0.01
# pos = 0
bet_size = 1 #0.001

while True:

    if datetime.datetime.now().second == 5:

        df = pd.read_csv(r'C:\Users\kenkot\OneDrive\桌面\Python learning\Calvin\Script\Production\Future + Glassnode\APE\data.csv')

        pos = signal(df, x, y)
        print(pos)

        trade(pos)

        time.sleep(555)

crypto/Future + Glassnode/APE/APE Trade.py


import torch
import os
import torchvision
import torch.optim as optim
import torchvision.transforms as transforms
from torch.autograd import Variable
import torch.nn as nn
import torch.utils.data.dataloader as Data

train_data = torchvision.datasets.CIFAR10(
    '/home/geniusrabbit/PycharmProjects/TestPyTF/cifar10', train=True, transform=transforms.ToTensor()
)

test_data = torchvision.datasets.CIFAR10(
    '/home/geniusrabbit/PycharmProjects/TestPyTF/cifar10', train=False, transform=transforms.ToTensor()
)
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

train_loader = Data.DataLoader(dataset=train_data, batch_size=4, shuffle=True, num_workers=2)
test_loader = Data.DataLoader(dataset=test_data, batch_size=4, shuffle=False, num_workers=2)

cfg = {
    'VGG11': [64, 'M', 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'VGG13': [64, 64, 'M', 128, 128, 'M', 256, 256, 'M', 512, 512, 'M', 512, 512, 'M'],
    'VGG16': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 'M', 512, 512, 512, 'M', 512, 512, 512, 'M'],
    'VGG19': [64, 64, 'M', 128, 128, 'M', 256, 256, 256, 256, 'M', 512, 512, 512, 512, 'M', 512, 512, 512, 512, 'M'],
}

class VGG(nn.Module):
    def __init__(self,vgg_name):
        super(VGG, self).__init__()
        self.features = self._makelayers(cfg[vgg_name])
        self.classifier = nn.Linear(512, 10)

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size(0), -1)
        x = self.classifier(x)
        return x

    def _makelayers(self, cfg):
        layers = []
        inplanes =3
        for x in cfg:
            if x == 'M':
                layers += [nn.MaxPool2d(kernel_size=2, stride=2)]
            else:
                layers += [nn.Conv2d(inplanes, x, kernel_size=3, padding=1),
                           nn.BatchNorm2d(x),
                           nn.ReLU(True)]
                inplanes = x
        layers += [nn.AvgPool2d(kernel_size=1, stride=1)]
        return nn.Sequential(*layers)

# net = VGG('VGG11')

net = torch.load('VGGNet.pkl')
optimizer = optim.SGD(net.parameters(), lr=0.01)
loss_func = nn.CrossEntropyLoss()

for epoch in range(50):
    train_acc1 = 0
    for i, data in enumerate(test_loader, 0):
        inputs, lables = data
        inputs, lables = Variable(inputs), Variable(lables)
        out = net(inputs)
        pred = torch.max(out, 1)[1]
        train_acc1 += (pred == lables).sum().item()
        if i % 1000 == 999:
            print('testDataAcc:[%d,%5d] acc: %.6f' % (epoch + 1, i + 1, train_acc1 / (1000 * lables.size(0))))
            train_acc1 = 0

    train_loss = 0
    train_acc = 0
    train_loss1 = 0
    for i, data in enumerate(train_loader, 0):

        inputs, lables = data
        inputs, lables = Variable(inputs), Variable(lables)

        optimizer.zero_grad()

        out = net(inputs)
        loss = loss_func(out, lables)
        train_loss += loss.item()

        pred = torch.max(out, 1)[1]
        train_acc += (pred == lables).sum().item()

        loss.backward()
        optimizer.step()

        train_loss += loss.data.item()

        if i % 100 == 99:
            print('[%d,%5d] loss:%.6f' % (epoch+1, i+1, train_loss/100))
            train_loss1 = train_loss1+train_loss
            train_loss = 0

        if i % 2000 == 1999:
            print('>>>>[%d,%5d] loss:%.6f' % (epoch + 1, i + 1, train_loss1 / 2000))
            print('>>>>[%d,%5d] acc: %.6f' % (epoch + 1, i + 1, train_acc / (2000 * lables.size(0))))
            train_acc = 0
            train_loss1 = 0
            torch.save(net, 'VGGNet.pkl')


import os
from MTM import matchTemplates, drawBoxesOnRGB
import cv2
import numpy as np

roi_temp_path = './template_image/template_roi/0.jpg'     # roi区域模板图
datamatrix_temp_path = './template_image/template_roi/datamatrix_template.jpg'      # datamatrix的template
fuse_temp_path = './template_image/template_fuse/'
temp_roi = cv2.imread(roi_temp_path, 0)
datamatrix_temp_roi = cv2.imread(datamatrix_temp_path, 0)

def readTemplates(temp_path):
    """用于读取检测的保险丝模板"""
    listTemplate = []
    folder_list = os.listdir(temp_path)
    for folder in folder_list:
        folder_path = os.path.join(temp_path, folder + '/')
        file_list = os.listdir(folder_path)
        for file in file_list:
            filepath = os.path.join(folder_path, file)
            # print(filepath)
            temp = cv2.imread(filepath, 0)  # 读取每个模板
            listTemplate.append((folder, temp))
    return listTemplate

def detect_fuse(image):
    """用于检测图片中的保险丝"""
    listTemplate = readTemplates(fuse_temp_path)
    for i, index in enumerate([2, 5, 10, 15, 20, 30]):
        if index == 30:
            for k_num in range(4):
                rotated = np.rot90(listTemplate[i][1], k=k_num)  # NB: np.rotate not good here, turns into float!
                listTemplate.append((str(index), rotated))
        else:
            rotated = np.rot90(listTemplate[i][1], k=2)  # NB: np.rotate not good here, turns into float!
            listTemplate.append((str(index), rotated))
    # score_threshold 用于设置置信度
    Hits = matchTemplates(listTemplate, image, score_threshold=0.45, method=cv2.TM_CCOEFF_NORMED, maxOverlap=0.3)
    Overlay = drawBoxesOnRGB(image, Hits, showLabel=True)
    return Overlay, Hits  # 返回输出图片和结果信息

def detect_roi(image):
    """用于检测图片中的保险丝盒区域"""
    listTemplate = [('roi', temp_roi)]
    Hits = matchTemplates(listTemplate, image, score_threshold=0.03, method=cv2.TM_CCOEFF_NORMED, maxOverlap=0.2)
    bbox = Hits['BBox'][0]
    # Overlay = drawBoxesOnRGB(image, Hits, showLabel=True)
    return bbox

def detect_datamatrix_roi(image):
    """用于检测图片中的二维码区域"""
    listTemplate = [('roi', datamatrix_temp_roi)]
    Hits = matchTemplates(listTemplate, image, score_threshold=0.3, method=cv2.TM_CCOEFF_NORMED, maxOverlap=0.2)
    try:
        bbox = Hits['BBox'][0]
        Overlay = drawBoxesOnRGB(image, Hits, showLabel=True)
    except IndexError:
        bbox = ()
    return bbox

def detect_fuse_line1(image):
    """用于检测图片中的保险丝盒第1行"""
    width = image.shape[0]
    length = image.shape[1]
    length_unit = int(length / 15)
    fuse_list = []
    for i in range(14):
        result_line1 = image[0:int(width / 3.5), int(length_unit * (i + 0.3)): int(length_unit * (i + 1.7))]
        result, result_info = detect_fuse(result_line1)
        if result_info.empty == False:
            '''tolist 可以不带索引名输出'''
            if len(result_info.loc[:, 'TemplateName'].tolist()) == 1:
                fuse_list.append(result_info.loc[:, 'TemplateName'].tolist()[0])  # 区域内只有一个保险丝
            else:
                print('Error! ' + str(len(result_info.loc[:, 'TemplateName'].tolist())) + ' outcomes in location line1_' + str(i + 1))  # 区域内有多个保险丝
                print(result_info.loc[:, 'TemplateName'].tolist())
                fuse_list.append('X')
        else:
            fuse_list.append('0')

    return fuse_list

def detect_fuse_line2(image):
    """用于检测图片中的保险丝盒第2行"""
    width = image.shape[0]
    length = image.shape[1]
    length_unit = int(length / 15)
    fuse_list = []
    for i in range(4):
        result_line2 = image[int(width / 4): int(2* width/4), int(length_unit * (i + 0.3)): int(length_unit * (i + 1.7))]
        result, result_info = detect_fuse(result_line2)
        # cv2.imshow(str(i), result)
        # cv2.waitKey(0)
        if result_info.empty == False:
            '''tolist 可以不带索引名输出'''
            if len(result_info.loc[:, 'TemplateName'].tolist()) == 1:
                fuse_list.append(result_info.loc[:, 'TemplateName'].tolist()[0])  # 区域内只有一个保险丝
            else:
                print('Error! ' + str(len(result_info.loc[:, 'TemplateName'].tolist())) + 'outcomes in location '
                                                                                'line2_' + str(i + 1))
                # 区域内有多个保险丝
                print(result_info.loc[:, 'TemplateName'].tolist())
                fuse_list.append('X')
        else:
            fuse_list.append('0')

    for j in range(4, 8):
        length_unit_big = int(length / 9)
        result_line2 = image[int(width / 4): int(2 * width / 4),
                       int(length_unit_big * (j - 1.3)): int(length_unit_big * (j - 0.3))]
        result, result_info = detect_fuse(result_line2)
        # cv2.imshow(str(j), result)
        # cv2.waitKey(0)
        if result_info.empty == False:
            '''tolist 可以不带索引名输出'''
            temp_fuse_list = result_info.loc[:, 'TemplateName'].tolist()  # 存放具有两个30的大块保险丝标签
            # print(temp_fuse_list)
            if len(temp_fuse_list) == 1:
                fuse_list.append(temp_fuse_list[0])  # 区域内只有一个保险丝
            elif len(temp_fuse_list) == 2 and temp_fuse_list[0] == '30':
                # print(temp_fuse_list)
                fuse_list.append(temp_fuse_list[0])  # 区域内有两个保险丝，但是都是30
            else:
                print('Error! ' + str(len(result_info.loc[:, 'TemplateName'].tolist())) + 'outcomes in location '
                                                                                'line2_' + str(j + 1))  #
                # 区域内有多个保险丝
                print(temp_fuse_list)
                cv2.imshow('temp', result)
                cv2.waitKey(0)
                fuse_list.append('X')
        else:
            fuse_list.append('0')

    return fuse_list

def detect_fuse_line3(image):
    """用于检测图片中的保险丝盒第3行"""
    width = image.shape[0]
    length = image.shape[1]
    length_unit = int(length / 15)
    fuse_list = []
    r = [range(0, 2), range(6, 8)]
    for small_range in r:
        for i in small_range:
            result_line3 = image[int(2 * width / 4):int(3 * width / 4), int(length_unit * (i + 0.3)): int(length_unit * (i + 1.7))]
            result, result_info = detect_fuse(result_line3)
            # cv2.imshow(str(i),result)
            # cv2.waitKey(0)
            if result_info.empty == False:
                '''tolist 可以不带索引名输出'''
                if len(result_info.loc[:, 'TemplateName'].tolist()) == 1:
                    fuse_list.append(result_info.loc[:, 'TemplateName'].tolist()[0])  # 区域内只有一个保险丝
                else:
                    print('Error! ' + str(len(result_info.loc[:, 'TemplateName'].tolist())) + ' outcomes in location line3_' + str(i + 1))  # 区域内有多个保险丝
                    print(result_info.loc[:, 'TemplateName'].tolist())

                    fuse_list.append('X')
            else:
                fuse_list.append('0')

    return fuse_list

def detect_fuse_line4(image):
    """用于检测图片中的保险丝盒第4行"""
    width = image.shape[0]
    length = image.shape[1]
    length_unit = int(length / 15)
    fuse_list = []
    r = [range(0, 2), range(6, 8)]
    for small_range in r:
        for i in small_range:
            result_line4 = image[int(2.8 * width / 4): width, int(length_unit * (i + 0.3)): int(length_unit * (i + 1.7))]
            result, result_info = detect_fuse(result_line4)
            # cv2.imshow(str(i),result)
            # cv2.waitKey(0)
            if result_info.empty == False:
                '''tolist 可以不带索引名输出'''
                if len(result_info.loc[:, 'TemplateName'].tolist()) == 1:
                    fuse_list.append(result_info.loc[:, 'TemplateName'].tolist()[0])  # 区域内只有一个保险丝
                else:
                    print('Error! ' + str(len(result_info.loc[:, 'TemplateName'].tolist())) + ' outcomes in location line4_' + str(i + 1))  # 区域内有多个保险丝
                    print(result_info.loc[:, 'TemplateName'].tolist())

                    fuse_list.append('X')
            else:
                fuse_list.append('0')

    return fuse_list

def change_list_to_matrix(temp_fuse_list):
    """用于将检测结果转换为包含0和1的矩阵"""
    fuse_matrix = []
    fuse_matrix_line1 = []
    fuse_matrix_line2 = []
    fuse_matrix_line3 = []
    fuse_matrix_line4 = []
    for i in range(14):
        if temp_fuse_list[0][i] != '0':
            fuse_matrix_line1.append(1)
        else:
            fuse_matrix_line1.append(0)
    for j in range(8):
        if temp_fuse_list[1][j] != '0':
            fuse_matrix_line2.append(1)
        else:
            fuse_matrix_line2.append(0)
    for k in range(4):
        if temp_fuse_list[2][k] != '0':
            fuse_matrix_line3.append(1)
        else:
            fuse_matrix_line3.append(0)
    for l in range(4):
        if temp_fuse_list[3][l] != '0':
            fuse_matrix_line4.append(1)
        else:
            fuse_matrix_line4.append(0)
    fuse_matrix.append(fuse_matrix_line1)
    fuse_matrix.append(fuse_matrix_line2)
    fuse_matrix.append(fuse_matrix_line3)
    fuse_matrix.append(fuse_matrix_line4)
    return fuse_matrix

def compare_list(detect_list, template_list, file):
    if detect_list == template_list:
        print(file, 'True')
    else:
        print(file, 'False')


# A part of NonVisual Desktop Access (NVDA)
# This file is covered by the GNU General Public License.
# See the file COPYING for more details.
# Copyright (C) 2010-2023 NV Access Limited, Babbage B.V., Mozilla Corporation, Cyrille Bougot,
# Leonard de Ruijter

"""Core framework for handling input from the user.
Every piece of input from the user (e.g. a key press) is represented by an L{InputGesture}.
The singleton L{InputManager} (L{manager}) manages functionality related to input from the user.
For example, it is used to execute gestures and handle input help.
"""

import sys
import os
import weakref
import time
from typing import (
	Any,
	Dict,
	Generator,
	List,
	Optional,
	Tuple,
	TypeVar,
	Union,
)
from gui import blockAction
import configobj
from speech import sayAll
import baseObject
import scriptHandler
import queueHandler
import api
import speech
import characterProcessing
import config
from fileUtils import FaultTolerantFile
import watchdog
from logHandler import log
import globalVars
import languageHandler
import controlTypes
import winKernel
import extensionPoints
from NVDAState import WritePaths

InputGestureBindingClassT = TypeVar("InputGestureBindingClassT")
ScriptNameT = str
InputGestureScriptT = Tuple[InputGestureBindingClassT, Optional[ScriptNameT]]
"""
The Python class and script name for each script;
the script name may be C{None} indicating that the gesture should be unbound for this class.
"""

#: Script category for emulated keyboard keys.
# Translators: The name of a category of NVDA commands.
SCRCAT_KBEMU = _("Emulated system keyboard keys")
#: Script category for miscellaneous commands.
# Translators: The name of a category of NVDA commands.
SCRCAT_MISC = _("Miscellaneous")
#: Script category for Browse Mode  commands.
# Translators: The name of a category of NVDA commands.
SCRCAT_BROWSEMODE = _("Browse mode")

class NoInputGestureAction(LookupError):
	"""Informs that there is no action to execute for a gesture.
	"""

class InputGesture(baseObject.AutoPropertyObject):
	"""A single gesture of input from the user.
	For example, this could be a key press on a keyboard or Braille display or a click of the mouse.
	At the very least, subclasses must implement L{_get_identifiers}.
	"""
	cachePropertiesByDefault = True

	#: indicates that sayAll was running before this gesture
	#: @type: bool
	wasInSayAll=False

	#: Indicates that while in Input Help Mode, this gesture should be handled as if Input Help mode was currently off.
	#: @type: bool
	bypassInputHelp=False

	#: Indicates that this gesture should be reported in Input help mode. This would only be false
	#: for flooding Gestures like touch screen hovers.
	#: @type: bool
	reportInInputHelp=True

	#: Indicates whether executing this gesture should explicitly prevent the system from being idle.
	#: For example, the system is unaware of C{BrailleDisplayGesture} execution,
	#: and might even get into sleep mode when reading a long portion of text in braille.
	#: In contrast, the system is aware of C{KeyboardInputGesture} execution itself.
	shouldPreventSystemIdle: bool = False

	# typing information for auto property _get_identifiers
	identifiers: Union[List[str], Tuple[str, ...]]

	_abstract_identifiers = True
	def _get_identifiers(self):
		"""The identifier(s) which will be used in input gesture maps to represent this gesture.
		These identifiers will be normalized and looked up in order until a match is found.
		A single identifier should take the form: C{source:id}
		where C{source} is a few characters representing the source of this gesture
		and C{id} is the specific gesture.
		An example identifier is: C{kb(desktop):NVDA+1}

		This property should not perform normalization itself.
		However, please note the following regarding normalization.
		If C{id} contains multiple chunks separated by a + sign, they are considered to be ordered arbitrarily
		and may be reordered when normalized.
		Normalization also ensures that the entire identifier is lower case.
		For example, NVDA+control+f1 and control+nvda+f1 will match when normalized.
		See L{normalizeGestureIdentifier} for more details.

		Subclasses must implement this method.
		@return: One or more identifiers which uniquely identify this gesture.
		@rtype: list or tuple of str
		"""
		raise NotImplementedError

	# type information for auto property _get_normalizedIdentifiers
	normalizedIdentifiers: List[str]

	def _get_normalizedIdentifiers(self):
		"""The normalized identifier(s) for this gesture.
		This just normalizes the identifiers returned in L{identifiers}
		by calling L{normalizeGestureIdentifier} for each identifier.
		These normalized identifiers can be directly looked up in input gesture maps.
		Subclasses should not override this method.
		@return: One or more normalized identifiers which uniquely identify this gesture.
		@rtype: list of str
		"""
		return [normalizeGestureIdentifier(identifier) for identifier in self.identifiers]

	# type information for auto property _get_displayName
	displayName: str

	def _get_displayName(self):
		"""The name of this gesture as presented to the user.
		The base implementation calls L{getDisplayTextForIdentifier} for the first identifier.
		Subclasses need not override this unless they wish to provide a more optimal implementation.
		@return: The display name.
		@rtype: str
		"""
		return self.getDisplayTextForIdentifier(self.normalizedIdentifiers[0])[1]

	#: Whether this gesture should be reported when reporting of command gestures is enabled.
	#: @type: bool
	shouldReportAsCommand = True

	#: whether this gesture represents a character being typed (i.e. not a potential command)
	#: @type bool
	isCharacter=False

	SPEECHEFFECT_CANCEL = "cancel"
	SPEECHEFFECT_PAUSE = "pause"
	SPEECHEFFECT_RESUME = "resume"
	#: The effect on speech when this gesture is executed; one of the SPEECHEFFECT_* constants or C{None}.
	speechEffectWhenExecuted = SPEECHEFFECT_CANCEL

	#: Whether this gesture is only a modifier, in which case it will not search for a script to execute.
	#: @type: bool
	isModifier = False

	def reportExtra(self):
		"""Report any extra information about this gesture to the user.
		This is called just after command gestures are reported.
		For example, it could be used to report toggle states.
		"""

	def _get_script(self):
		"""The script bound to this input gesture.
		@return: The script to be executed.
		@rtype: script function
		"""
		self.script=scriptHandler.findScript(self)
		return self.script

	def send(self):
		"""Send this gesture to the operating system.
		This is not possible for all sources.
		@raise NotImplementedError: If the source does not support sending of gestures.
		"""
		raise NotImplementedError

	#: typing information for autoproperty _get_scriptableObject
	scriptableObject: Optional[baseObject.ScriptableObject]

	def _get_scriptableObject(self) -> Optional[baseObject.ScriptableObject]:
		"""An object which contains scripts specific to this  gesture or type of gesture.
		This object will be searched for scripts before any other object when handling this gesture.
		@return: The gesture specific scriptable object or C{None} if there is none.
		"""
		return None

	@classmethod
	def getDisplayTextForIdentifier(cls, identifier):
		"""Get the text to be presented to the user describing a given gesture identifier.
		This should only be called with normalized gesture identifiers returned by the
		L{normalizedIdentifiers} property in the same subclass.
		For example, C{KeyboardInputGesture.getDisplayTextForIdentifier} should only be called
		for "kb:*" identifiers returned by C{KeyboardInputGesture.normalizedIdentifiers}.
		Most callers will want L{inputCore.getDisplayTextForIdentifier} instead.
		The display text consists of two strings:
		the gesture's source (e.g. "laptop keyboard")
		and the specific gesture (e.g. "alt+tab").
		@param identifier: The normalized gesture identifier in question.
		@type identifier: str
		@return: A tuple of (source, specificGesture).
		@rtype: tuple of (str, str)
		@raise Exception: If no display text can be determined.
		"""
		raise NotImplementedError

	def executeScript(self, script):
		"""
		Executes the given script with this gesture, using scriptHandler.executeScript.
		This is only implemented so as to allow Gesture subclasses
		to perform an action directly before / after the script executes.
		"""
		return scriptHandler.executeScript(script, self)

FlattenedGestureMapT = Dict[
	str,  # moduleName.className
	Dict[
		Optional[ScriptNameT],  # Script name
		Optional[Union[str, List[str]]],  # Normalized gestures
	],
]
_InternalGestureMapT = Dict[
	str,  # Normalized gesture
	List[
		Tuple[
			str,  # module
			str,  # class name
			Optional[ScriptNameT],  # script
		],
	],
]

class GlobalGestureMap:
	"""Maps gestures to scripts anywhere in NVDA.
	This is used to allow users and locales to bind gestures in addition to those bound by
	individual scriptable objects.
	Map entries will most often be loaded from a file using the L{load} method.
	See that method for details of the file format.
	"""

	def __init__(self, entries: Optional[FlattenedGestureMapT] = None):
		"""Constructor.
		@param entries: Initial entries to add; see L{update} for the format.
		"""
		self._map: _InternalGestureMapT = {}
		#: Indicates that the last load or update contained an error.
		self.lastUpdateContainedError: bool = False
		#: The file name for this gesture map, if any.
		self.fileName: Optional[str] = None
		if entries:
			self.update(entries)

	def clear(self):
		"""Clear this map.
		"""
		self._map.clear()
		self.lastUpdateContainedError = False

	def add(
			self,
			gesture: str,
			module: str,
			className: str,
			script: Optional[ScriptNameT],
			replace: bool = False
	):
		"""Add a gesture mapping.
		@param gesture: The gesture identifier.
		@param module: The name of the Python module containing the target script.
		@param className: The name of the class in L{module} containing the target script.
		@param script: The name of the target script
			or C{None} to unbind the gesture for this class.
		@param replace: if true replaces all existing bindings for this gesture with the given script,
			otherwise only appends this binding.
		"""
		gesture = normalizeGestureIdentifier(gesture)
		try:
			scripts = self._map[gesture]
		except KeyError:
			scripts = self._map[gesture] = []
		if replace:
			del scripts[:]
		scripts.append((module, className, script))

	def load(self, filename: str):
		"""Load map entries from a file.
		The file is an ini file.
		Each section contains entries for a particular scriptable object class.
		The section name must be the full Python module and class name.
		The key of each entry is the script name and the value is a comma separated list of one or more gestures.
		If the script name is "None", the gesture will be unbound for this class.
		For example, the following binds the "a" key to move to the next heading in virtual buffers
		and removes the default "h" binding::
			[virtualBuffers.VirtualBuffer]
			nextHeading = kb:a
			None = kb:h
		@param filename: The name of the file to load.
		"""
		self.fileName = filename
		try:
			conf = configobj.ConfigObj(filename, file_error=True, encoding="UTF-8")
		except (configobj.ConfigObjError, UnicodeDecodeError) as e:
			log.warning("Error in gesture map '%s': %s"%(filename, e))
			self.lastUpdateContainedError = True
			return
		self.update(conf)

	def update(self, entries: FlattenedGestureMapT):
		"""Add multiple map entries.
		C{entries} must be a mapping of mappings.
		Each inner mapping contains entries for a particular scriptable object class.
		The key in the outer mapping must be the full Python module and class name.
		The key of each entry in the inner mappings is the script name
		and the value is one gesture string or a list of one or more gesture strings.
		If the script name is C{None}, the gesture will be unbound for this class.
		For example, the following binds the "a" key to move to the next heading in virtual buffers
		and removes the default "h" binding:
			{
				"virtualBuffers.VirtualBuffer": {
					"nextHeading": "kb:a",
					None: "kb:h",
				}
			}
		@param entries: The items to add.
		"""
		self.lastUpdateContainedError = False
		for locationName, location in entries.items():
			try:
				module, className = locationName.rsplit(".", 1)
			except:
				log.error("Invalid module/class specification: %s" % locationName)
				self.lastUpdateContainedError = True
				continue
			for script, gestures in location.items():
				if script == "None":
					script = None
				if gestures in ("", None):
					gestures = ()
				elif isinstance(gestures, str):
					gestures = [gestures]
				for gesture in gestures:
					try:
						self.add(gesture, module, className, script)
					except:
						log.error("Invalid gesture: %s" % gesture)
						self.lastUpdateContainedError = True
						continue

	def getScriptsForGesture(self, gesture: str) -> Generator[InputGestureScriptT, None, None]:
		"""Get the scripts associated with a particular gesture.
		@param gesture: The gesture identifier.
		@return: The Python class and script name for each script;
			the script name may be C{None} indicating that the gesture should be unbound for this class.
		"""
		try:
			scripts = self._map[gesture]
		except KeyError:
			return
		for moduleName, className, scriptName in scripts:
			try:
				module = sys.modules[moduleName]
			except KeyError:
				continue
			try:
				cls = getattr(module, className)
			except AttributeError:
				continue
			yield cls, scriptName

	def getScriptsForAllGestures(self):
		"""Get all of the scripts and their gestures.
		@return: The Python class, gesture and script name for each mapping;
			the script name may be C{None} indicating that the gesture should be unbound for this class.
		@rtype: generator of (class, str, str)
		"""
		for gesture in self._map:
			for cls, scriptName in self.getScriptsForGesture(gesture):
				yield cls, gesture, scriptName

	def remove(self, gesture: str, module: str, className: str, script: ScriptNameT):
		"""Remove a gesture mapping.
		@param gesture: The gesture identifier.
		@param module: The name of the Python module containing the target script.
		@param className: The name of the class in L{module} containing the target script.
		@param script: The name of the target script.
		@raise ValueError: If the requested mapping does not exist.
		"""
		gesture = normalizeGestureIdentifier(gesture)
		try:
			scripts = self._map[gesture]
		except KeyError:
			raise ValueError("Mapping not found")
		scripts.remove((module, className, script))

	def export(self) -> FlattenedGestureMapT:
		"""Exports this gesture map to a dictionary that can be saved to disk or imported into another gesture map.
		"""
		out: FlattenedGestureMapT = {}
		for gesture, scripts in self._map.items():
			for module, className, script in scripts:
				key = f"{module}.{className}"
				try:
					outSect = out[key]
				except KeyError:
					out[key] = {}
					outSect = out[key]
				if script is None:
					script = "None"
				try:
					outVal = outSect[script]
				except KeyError:
					# Write the first value as a string so configobj doesn't output a comma if there's only one value.
					outVal = outSect[script] = gesture
				else:
					if isinstance(outVal, list):
						outVal.append(gesture)
					else:
						outSect[script] = [outVal, gesture]
		return out

	@blockAction.when(blockAction.Context.SECURE_MODE)
	def save(self):
		"""Save this gesture map to disk.
		@precondition: L{load} must have been called.
		"""
		if not self.fileName:
			raise ValueError("No file name")
		out = configobj.ConfigObj(self.export(), encoding="UTF-8")
		out.filename = self.fileName

		with FaultTolerantFile(out.filename) as f:
			out.write(f)

	def __eq__(self, other: Any) -> bool:
		if isinstance(other, GlobalGestureMap):
			return self._map == other._map
		return NotImplemented

decide_executeGesture = extensionPoints.Decider()
"""
Notifies when a gesture is about to be executed,
and allows components or add-ons to decide whether or not to execute a gesture.
For example, when controlling a remote system with a connected local braille display,
braille display gestures should not be executed locally.
Handlers are called with one argument:
@param gesture: The gesture that is about to be executed.
@type gesture: L{InputGesture}
"""

class InputManager(baseObject.AutoPropertyObject):
	"""Manages functionality related to input from the user.
	Input includes key presses on the keyboard, as well as key presses on Braille displays, etc.
	"""

	#: a modifier gesture was just executed while sayAll was running
	#: @type: bool
	lastModifierWasInSayAll=False

	def __init__(self):
		#: The function to call when capturing gestures.
		#: If it returns C{False}, normal execution will be prevented.
		#: @type: callable
		self._captureFunc = None
		#: The gestures mapped for the NVDA locale.
		#: @type: L{GlobalGestureMap}
		self.localeGestureMap = GlobalGestureMap()
		#: The gestures mapped by the user.
		#: @type: L{GlobalGestureMap}
		self.userGestureMap = GlobalGestureMap()
		self.loadLocaleGestureMap()
		self.loadUserGestureMap()
		self._lastInputTime = None

	def executeGesture(self, gesture):
		"""Perform the action associated with a gesture.
		@param gesture: The gesture to execute.
		@type gesture: L{InputGesture}
		@raise NoInputGestureAction: If there is no action to perform.
		"""
		if watchdog.isAttemptingRecovery:
			# The core is dead, so don't try to perform an action.
			# This lets gestures pass through unhindered where possible,
			# as well as stopping a flood of actions when the core revives.
			raise NoInputGestureAction

		if not decide_executeGesture.decide(gesture=gesture):
			# A registered handler decided that this gesture shouldn't be executed.
			# Purposely do not raise a NoInputGestureAction here, as that could
			# lead to unexpected behavior for gesture emulation, i.e. the gesture will be send to the system
			# when the decider decided not to execute it.
			log.debug(
				"Gesture execution canceled by handler registered to decide_executeGesture extension point"
			)
			return

		script = gesture.script
		focus = api.getFocusObject()
		if focus.sleepMode is focus.SLEEP_FULL or (focus.sleepMode and not getattr(script, 'allowInSleepMode', False)):
			raise NoInputGestureAction

		wasInSayAll=False
		if gesture.isModifier:
			if not self.lastModifierWasInSayAll:
				wasInSayAll = self.lastModifierWasInSayAll = sayAll.SayAllHandler.isRunning()
		elif self.lastModifierWasInSayAll:
			wasInSayAll=True
			self.lastModifierWasInSayAll=False
		else:
			wasInSayAll = sayAll.SayAllHandler.isRunning()
		if wasInSayAll:
			gesture.wasInSayAll=True

		immediate = getattr(gesture, "_immediate", True)
		speechEffect = gesture.speechEffectWhenExecuted
		if speechEffect == gesture.SPEECHEFFECT_CANCEL:
			queueHandler.queueFunction(queueHandler.eventQueue, speech.cancelSpeech, _immediate=immediate)
		elif speechEffect in (gesture.SPEECHEFFECT_PAUSE, gesture.SPEECHEFFECT_RESUME):
			queueHandler.queueFunction(queueHandler.eventQueue, speech.pauseSpeech, speechEffect == gesture.SPEECHEFFECT_PAUSE)

		if gesture.shouldPreventSystemIdle:
			winKernel.SetThreadExecutionState(winKernel.ES_SYSTEM_REQUIRED)

		if log.isEnabledFor(log.IO) and not gesture.isModifier:
			self._lastInputTime = time.time()
			log.io("Input: %s" % gesture.identifiers[0])

		if self._captureFunc:
			try:
				if self._captureFunc(gesture) is False:
					return
			except:
				log.error("Error in capture function, disabling", exc_info=True)
				self._captureFunc = None

		if gesture.isModifier:
			raise NoInputGestureAction

		if config.conf["keyboard"]["speakCommandKeys"] and gesture.shouldReportAsCommand:
			queueHandler.queueFunction(
				queueHandler.eventQueue,
				speech.speakMessage,
				gesture.displayName,
				_immediate=True
			)

		gesture.reportExtra()

		# #2953: if an intercepted command Script (script that sends a gesture) is queued
		# then queue all following gestures (that don't have a script) with a fake script so that they remain in order.
		if not script and scriptHandler._numIncompleteInterceptedCommandScripts:
			script=lambda gesture: gesture.send()

		if script:
			scriptHandler.queueScript(script, gesture)
			return
		else:
			# Clear memorized last script to avoid getLastScriptRepeatCount detect a repeat
			# in case an unbound gesture is executed between two identical bound gestures.
			queueHandler.queueFunction(queueHandler.eventQueue, scriptHandler.clearLastScript)
			raise NoInputGestureAction

	def _get_isInputHelpActive(self):
		"""Whether input help is enabled, wherein the function of each key pressed by the user is reported but not executed.
		@rtype: bool
		"""
		return self._captureFunc == self._inputHelpCaptor

	def _set_isInputHelpActive(self, enable):
		if enable:
			self._captureFunc = self._inputHelpCaptor
		elif self.isInputHelpActive:
			self._captureFunc = None

	def _inputHelpCaptor(self, gesture):
		bypass = gesture.bypassInputHelp or getattr(gesture.script, "bypassInputHelp", False)
		immediate = getattr(gesture, "_immediate", True)
		queueHandler.queueFunction(
			queueHandler.eventQueue,
			self._handleInputHelp,
			gesture,
			onlyLog=bypass or not gesture.reportInInputHelp,
			_immediate=immediate
		)
		return bypass

	def _handleInputHelp(self, gesture, onlyLog=False):
		textList = [gesture.displayName]
		script = gesture.script
		runScript = False
		logMsg = "Input help: gesture %s"%gesture.identifiers[0]
		if script:
			scriptName = scriptHandler.getScriptName(script)
			logMsg+=", bound to script %s" % scriptName
			scriptLocation = scriptHandler.getScriptLocation(script)
			if scriptLocation:
				logMsg += " on %s" % scriptLocation
			if scriptName == "toggleInputHelp":
				runScript = True
			else:
				desc = script.__doc__
				if desc:
					textList.append(desc)

		log.info(logMsg)
		if onlyLog:
			return

		import braille
		braille.handler.message("\t\t".join(textList))
		# Punctuation must be spoken for the gesture name (the first chunk) so that punctuation keys are spoken.
		speech.speakText(
			textList[0],
			reason=controlTypes.OutputReason.MESSAGE,
			symbolLevel=characterProcessing.SymbolLevel.ALL
		)
		for text in textList[1:]:
			speech.speakMessage(text)

		if runScript:
			script(gesture)

	def loadUserGestureMap(self):
		self.userGestureMap.clear()
		try:
			self.userGestureMap.load(WritePaths.gesturesConfigFile)
		except IOError:
			log.debugWarning("No user gesture map")

	def loadLocaleGestureMap(self):
		self.localeGestureMap.clear()
		lang = languageHandler.getLanguage()
		try:
			self.localeGestureMap.load(os.path.join(globalVars.appDir, "locale", lang, "gestures.ini"))
		except IOError:
			try:
				self.localeGestureMap.load(os.path.join(globalVars.appDir, "locale", lang.split('_')[0], "gestures.ini"))
			except IOError:
				log.debugWarning("No locale gesture map for language %s" % lang)

	def emulateGesture(self, gesture):
		"""Convenience method to emulate a gesture.
		First, an attempt will be made to execute the gesture using L{executeGesture}.
		If that fails, the gesture will be sent to the operating system if possible using L{InputGesture.send}.
		@param gesture: The gesture to execute.
		@type gesture: L{InputGesture}
		"""
		try:
			return self.executeGesture(gesture)
		except NoInputGestureAction:
			pass
		try:
			gesture.send()
		except NotImplementedError:
			pass

	def getAllGestureMappings(self, obj=None, ancestors=None):
		if not obj:
			obj = api.getFocusObject()
			ancestors = api.getFocusAncestors()
		return _AllGestureMappingsRetriever(obj, ancestors).results

class _AllGestureMappingsRetriever(object):

	results: Dict[
		str,  # category name
		Dict[
			str,  # command display name
			Any,  # AllGesturesScriptInfo
		]
	]

	def __init__(self, obj, ancestors):
		self.results = {}
		self.scriptInfo = {}
		self.handledGestures = set()

		self.addGlobalMap(manager.userGestureMap)
		self.addGlobalMap(manager.localeGestureMap)
		import braille
		gmap = braille.handler.display.gestureMap if braille.handler and braille.handler.display else None
		if gmap:
			self.addGlobalMap(gmap)

		# Global plugins.
		import globalPluginHandler
		for plugin in globalPluginHandler.runningPlugins:
			self.addObj(plugin)

		# App module.
		app = obj.appModule
		if app:
			self.addObj(app)

		# Braille display driver
		if isinstance(braille.handler.display, baseObject.ScriptableObject):
			self.addObj(braille.handler.display)

		# Vision enhancement provider
		import vision
		for provider in vision.handler.getActiveProviderInstances():
			if isinstance(provider, baseObject.ScriptableObject):
				self.addObj(provider)

		# Tree interceptor.
		ti = obj.treeInterceptor
		if ti:
			self.addObj(ti)

		# NVDAObject.
		self.addObj(obj)
		for anc in reversed(ancestors):
			self.addObj(anc, isAncestor=True)

		import globalCommands
		# Configuration profiles
		self.addObj(globalCommands.configProfileActivationCommands)

		# Global commands.
		self.addObj(globalCommands.commands)

	def addResult(self, scriptInfo):
		"""
		@type scriptInfo: AllGesturesScriptInfo
		"""
		self.scriptInfo[scriptInfo.cls, scriptInfo.scriptName] = scriptInfo
		try:
			cat = self.results[scriptInfo.category]
		except KeyError:
			cat = self.results[scriptInfo.category] = {}
		cat[scriptInfo.displayName] = scriptInfo

	def addGlobalMap(self, gmap):
		for cls, gesture, scriptName in gmap.getScriptsForAllGestures():
			key = (cls, gesture)
			if key in self.handledGestures:
				continue
			self.handledGestures.add(key)
			if scriptName is None:
				# The global map specified that no script should execute for this gesture and object.
				continue
			try:
				scriptInfo = self.scriptInfo[cls, scriptName]
			except KeyError:
				if scriptName.startswith("kb:"):
					scriptInfo = self.makeKbEmuScriptInfo(cls, kbGestureIdentifier=scriptName)
				else:
					try:
						script = getattr(cls, "script_%s" % scriptName)
					except AttributeError:
						log.debugWarning(f"Unable to bind gesture: script '{scriptName}' not found in class {cls}.")
						self.handledGestures.remove(key)
						continue
					scriptInfo = self.makeNormalScriptInfo(cls, scriptName, script)
					if not scriptInfo:
						# Scripts with no description are not displayed in the Input gesture dialog.
						continue
				self.addResult(scriptInfo)
			scriptInfo.gestures.append(gesture)

	@classmethod
	def makeKbEmuScriptInfo(cls, scriptCls, kbGestureIdentifier):
		"""
		@rtype AllGesturesScriptInfo
		"""
		info = KbEmuScriptInfo(scriptCls, kbGestureIdentifier)
		info.category = SCRCAT_KBEMU
		info.displayName = getDisplayTextForGestureIdentifier(
			normalizeGestureIdentifier(kbGestureIdentifier)
		)[1]
		return info

	@classmethod
	def makeNormalScriptInfo(cls, scriptCls, scriptName, script):
		info = AllGesturesScriptInfo(scriptCls, scriptName)
		info.category = cls.getScriptCategory(scriptCls, script)
		info.displayName = script.__doc__
		if not info.displayName:
			return None
		return info

	@classmethod
	def getScriptCategory(cls, scriptCls, script):
		try:
			return script.category
		except AttributeError:
			pass
		try:
			return scriptCls.scriptCategory
		except AttributeError:
			pass
		return SCRCAT_MISC

	def addObj(self, obj, isAncestor=False):
		scripts = {}
		for cls in obj.__class__.__mro__:
			for scriptName, script in cls.__dict__.items():
				if not scriptName.startswith("script_"):
					continue
				if isAncestor and not getattr(script, "canPropagate", False):
					continue
				scriptName = scriptName[7:]
				try:
					scriptInfo = self.scriptInfo[cls, scriptName]
				except KeyError:
					scriptInfo = self.makeNormalScriptInfo(cls, scriptName, script)
					if not scriptInfo:
						continue
					self.addResult(scriptInfo)
				scripts[script] = scriptInfo
		for gesture, script in obj._gestureMap.items():
			try:
				scriptInfo = scripts[script]
			except KeyError:
				continue
			key = (scriptInfo.cls, gesture)
			if key in self.handledGestures:
				continue
			self.handledGestures.add(key)
			scriptInfo.gestures.append(gesture)

class AllGesturesScriptInfo(object):
	__slots__ = ("cls", "scriptName", "category", "displayName", "gestures")

	def __init__(self, cls, scriptName):
		self.cls = cls
		self.scriptName = scriptName
		self.gestures = []

	@property
	def moduleName(self):
		return self.cls.__module__

	@property
	def className(self):
		return self.cls.__name__

class KbEmuScriptInfo(AllGesturesScriptInfo):
	pass

def normalizeGestureIdentifier(identifier):
	"""Normalize a gesture identifier so that it matches other identifiers for the same gesture.
	First, the entire identifier is converted to lower case.
	Then, any items separated by a + sign after the source prefix are considered to be of indeterminate order
	and are sorted by character.
	This is done because, for example, "kb:shift+alt+downArrow"
	must be treated the same as "kb:alt+shift+downarrow".
	"""
	identifier = identifier.lower()
	prefix, main = identifier.split(":", 1)
	main = main.split("+")
	# The order of the parts doesn't matter as far as the user is concerned,
	# but we need them to be in a determinate order so they will match other gesture identifiers.
	# We sort them by character.
	main.sort()
	main = "+".join(main)
	return u"{0}:{1}".format(prefix, main)

#: Maps registered source prefix strings to L{InputGesture} classes.
gestureSources = weakref.WeakValueDictionary()

def registerGestureSource(source, gestureCls):
	"""Register an input gesture class for a source prefix string.
	The specified gesture class will be used for queries regarding all gesture identifiers with the given source prefix.
	For example, if "kb" is registered with the C{KeyboardInputGesture} class,
	any queries for "kb:tab" or "kb(desktop):tab" will be directed to the C{KeyboardInputGesture} class.
	If there is no exact match for the source, any parenthesized portion is stripped.
	For example, for "br(baum):d1", if "br(baum)" isn't registered,
	"br" will be used if it is registered.
	This registration is used, for example, to get the display text for a gesture identifier.
	@param source: The source prefix for associated gesture identifiers.
	@type source: str
	@param gestureCls: The input gesture class.
	@type gestureCls: L{InputGesture}
	"""
	gestureSources[source] = gestureCls

def _getGestureClsForIdentifier(identifier):
	"""Get the registered gesture class for an identifier.
	"""
	source = identifier.split(":", 1)[0]
	try:
		return gestureSources[source]
	except KeyError:
		pass
	genSource = source.split("(", 1)[0]
	if genSource:
		try:
			return gestureSources[genSource]
		except KeyError:
			pass
	raise LookupError("Gesture source not registered: %s" % source)

def getDisplayTextForGestureIdentifier(identifier):
	"""Get the text to be presented to the user describing a given gesture identifier.
	The display text consists of two strings:
	the gesture's source (e.g. "laptop keyboard")
	and the specific gesture (e.g. "alt+tab").
	@param identifier: The normalized gesture identifier in question.
	@type identifier: str
	@return: A tuple of (source, specificGesture).
	@rtype: tuple of (str, str)
	@raise LookupError: If no display text can be determined.
	"""
	gcls = _getGestureClsForIdentifier(identifier)
	try:
		return gcls.getDisplayTextForIdentifier(identifier)
	except:
		raise
		raise LookupError("Couldn't get display text for identifier: %s" % identifier)

#: The singleton input manager instance.
manager: Optional[InputManager] = None

def initialize():
	"""Initializes input core, creating a global L{InputManager} singleton.
	"""
	global manager
	manager=InputManager()

def terminate():
	"""Terminates input core.
	"""
	global manager
	manager=None

def  logTimeSinceInput():
	"""Log the time since the last input was received.
	This does nothing if time since input logging is disabled.
	"""
	if (not log.isEnabledFor(log.IO)
		or not config.conf["debugLog"]["timeSinceInput"]
		or not manager or not manager._lastInputTime
	):
		return
	log.io("%.3f sec since input" % (time.time() - manager._lastInputTime))


from elasticsearch_dsl import Search, Mapping, Field, Index
from elasticsearch_dsl.connections import connections
from elasticsearch.helpers import bulk
from .helpers import chunk_queryset_iterator

DELETE_META_FIELDS = frozenset((
    'id', 'parent', 'routing', 'version', 'version_type'
))

DOC_META_FIELDS = frozenset((
    'timestamp', 'ttl'
)).union(DELETE_META_FIELDS)

META_FIELDS = frozenset((
    'index', 'using', 'score', 'doc_type', 'serializer'
)).union(DOC_META_FIELDS)

class MetaField(object):

    def __init__(self, *args, **kwargs):
        self.args, self.kwargs = args, kwargs

class DocTypeOptions(object):

    def __init__(self, name, bases, attrs):
        meta = attrs.pop('Meta', None)
        self.meta = meta
        self.index = getattr(meta, 'index', None)
        self.doc_type = getattr(meta, 'doc_type', None)
        self._using = getattr(meta, 'using', None)
        self.mapping = getattr(meta, 'mapping', Mapping(self.doc_type))
        self.serializer = getattr(meta, 'serializer', None)

        for name, value in list(attrs.items()):
            if isinstance(value, Field):
                self.mapping.field(name, value)
                del attrs[name]

        for name in dir(meta):
            if isinstance(getattr(meta, name, None), MetaField):
                params = getattr(meta, name)
                self.mapping.meta(name, *params.args, **params.kwargs)

        for b in bases:
            if hasattr(b, '_doc_type') and hasattr(b._doc_type, 'mapping'):
                self.mapping.update(b._doc_type.mapping, update_only=True)
                self._using = self._using or b._doc_type._using
                self.index = self.index or b._doc_type.index

    @property
    def using(self):
        return self._using or 'default'

    @property
    def model(self):
        if not self.serializer:
            return None
        return self.serializer.Meta.model

    @property
    def name(self):
        return self.mapping.properties.name

    @property
    def parent(self):
        if '_parent' in self.mapping._meta:
            return self.mapping._meta['_parent']['type']
        return

    def resolve_field(self, field_path):
        return self.mapping.resolve_field(field_path)

    def init(self, index=None, using=None):
        self.mapping.save(index or self.index, using=using or self.using)

    def refresh(self, index=None, using=None):
        self.mapping.update_from_es(
            index or self.index, using=using or self.using)

class DocMeta(type):

    def __new__(cls, name, bases, attrs):
        attrs['_doc_type'] = DocTypeOptions(name, bases, attrs)
        model = attrs['_doc_type'].model
        if model is not None:
            attrs['model_label'] = model._meta.label_lower
        return type.__new__(cls, name, bases, attrs)

class ModelSerializerDocument(object, metaclass=DocMeta):
    use_for_search = True

    def __init__(self, instance, meta={}):
        self.instance = instance
        self.serializer = self._doc_type.serializer(instance)
        self.meta = meta
        self.meta["id"] = instance.pk

    @classmethod
    def get_model(cls):
        return cls._doc_type.model

    @classmethod
    def action_meta(cls, x):
        action = {}
        action["_type"] = cls._doc_type.doc_type
        action["_id"] = x["id"]
        action["_source"] = x
        return action

    @classmethod
    def bulk_index(cls, row=[]):
        es = connections.get_connection()
        items = cls._doc_type.serializer(row, many=True).data
        actions = list(map(cls.action_meta, items))
        success, _ = bulk(
            es, actions, index=cls._doc_type.index, raise_on_error=True)
        print('Performed %d actions' % success)

    @classmethod
    def bulk_index_queryset(cls, queryset=[]):
        es = connections.get_connection()

        for row in chunk_queryset_iterator(queryset):
            items = cls._doc_type.serializer(row, many=True).data
            actions = list(map(cls.action_meta, items))
            success, _ = bulk(
                es, actions, index=cls._doc_type.index, raise_on_error=True)
            print('Performed %d actions' % success)

    @classmethod
    def init(cls, index=None, using=None):
        cls._doc_type.init(index, using)

    @classmethod
    def get(cls, id, using=None, index=None, **kwargs):
        es = connections.get_connection(using or cls._doc_type.using)
        doc = es.get(
            index=index or cls._doc_type.index,
            doc_type=cls._doc_type.name,
            id=id,
            **kwargs
        )
        if not doc['found']:
            return None
        return doc["_source"]

    @classmethod
    def search(cls, using=None, index=None):
        return Search(
            using=using or cls._doc_type.using,
            index=index or cls._doc_type.index,
            doc_type=[cls._doc_type.doc_type]
        )

    @classmethod
    def refresh_index(cls):
        index = Index(cls._doc_type.index)
        return index.refresh()

    def _get_index(self, index=None):
        if index is None:
            index = getattr(self.meta, 'index', self._doc_type.index)
        if index is None:
            raise Exception('No index')
        return index

    def delete(self, using=None, index=None, **kwargs):
        es = connections.get_connection()
        doc_meta = dict(
            (k, self.meta[k])
            for k in DELETE_META_FIELDS
            if k in self.meta
        )

        doc_meta.update(kwargs)
        es.delete(
            index=self._get_index(),
            doc_type=self._doc_type.name,
            **doc_meta
        )

    def save(self, using=None, index=None, **kwargs):
        es = connections.get_connection()

        doc_meta = dict(
            (k, self.meta[k])
            for k in DOC_META_FIELDS
            if k in self.meta
        )
        doc_meta.update(kwargs)

        meta = es.index(
            index=self._get_index(),
            doc_type=self._doc_type.name,
            body=self.serializer.data,
            **doc_meta
        )

        return meta


#! -*- coding:utf-8 -*-
import numpy as np
import tensorflow as tf
from data_process import Token, get_input, id2predicate
import json

train_data = json.load(open('./data_trans/train_data_me.json', encoding='utf-8'))
dev_data = json.load(open('./data_trans/dev_data_me.json', encoding='utf-8'))

num_class = 49
lr = 0.001
num_epochs = 20
batch_size = 32

class data_loader():
    def __init__(self):
        self.input_x, self.input_ner1, self.input_ner2, self.input_re1, self.input_re2, self.p_s, self.p_e = get_input(train_data)
        self.input_x = self.input_x.astype(np.int32)
        self.input_ner1 = self.input_ner1.astype(np.int32)
        self.input_ner2 = self.input_ner2.astype(np.int32)
        self.input_re1 = self.input_re1.astype(np.float32)
        self.input_re2 = self.input_re2.astype(np.float32)
        self.p_s = self.p_s.astype(np.int32)
        self.p_e = self.p_e.astype(np.int32)
        self.num_train = self.input_x.shape[0]
        self.db_train = tf.data.Dataset.from_tensor_slices((self.input_x, self.input_ner1, self.input_ner2, self.input_re1, self.input_re2, self.p_s, self.p_e))
        self.db_train = self.db_train.shuffle(self.num_train).batch(batch_size, drop_remainder=True)

    def get_batch(self, batch_s):
        indics = np.random.randint(0, self.num_train, batch_s)
        return self.input_x[indics], self.input_ner1[indics], self.input_ner2[indics], self.input_re1[indics], self.input_re2[indics], self.p_s[indics], self.p_e[indics]

class Ner_model(tf.keras.Model):
    def __init__(self):
        super(Ner_model, self).__init__()
        self.char_embedding = tf.keras.layers.Embedding(4996, 64, mask_zero=True)
        self.bi_gru = tf.keras.layers.Bidirectional(tf.keras.layers.GRU(64, return_sequences=True))
        self.dense_1 = tf.keras.layers.Dense(1)
        self.dense_2 = tf.keras.layers.Dense(1)

    def call(self, inputs):
        x = self.char_embedding(inputs)
        mask = self.char_embedding.compute_mask(inputs)
        x_gru = self.bi_gru(x, mask=mask)
        x_1 = tf.nn.sigmoid(self.dense_1(x_gru))
        x_2 = tf.nn.sigmoid(self.dense_2(x_gru))
        return x_1, x_2, x_gru

class ER_model(tf.keras.Model):
    def __init__(self):
        super(ER_model, self).__init__()
        self.dense_1 = tf.keras.layers.Dense(num_class)
        self.dense_2 = tf.keras.layers.Dense(num_class)
        self.average = tf.keras.layers.Average()

    def call(self, x_lstm, position_s, position_e):
        add_encode = np.zeros_like(x_lstm)
        for i, k in enumerate(position_s):
            gru_v = x_lstm[i, :, :]
            v_s = gru_v[k, :]
            v_e = gru_v[position_e[i], :]
            v_subject = self.average([v_s, v_e])
            add_encode[i, k, :] = v_subject
            add_encode[i, position_e[i], :] = v_subject

        x = x_lstm + add_encode
        output1 = tf.sigmoid(self.dense_1(x))
        output2 = tf.sigmoid(self.dense_2(x))
        return output1, output2

def loss_function(y_1, y_2, y_re1, y_re2, input_ner1, input_ner2, input_re1, input_re2):
    input_ner1 = tf.expand_dims(input_ner1, 2)
    loss_ner1 = tf.keras.losses.binary_crossentropy(y_true=input_ner1, y_pred=y_1)
    loss_ner1 = tf.reduce_sum(loss_ner1)

    input_ner2 = tf.expand_dims(input_ner2, 2)
    loss_ner2 = tf.keras.losses.binary_crossentropy(y_true=input_ner2, y_pred=y_2)
    loss_ner2 = tf.reduce_sum(loss_ner2)

    loss_re1 = tf.reduce_sum(tf.keras.losses.binary_crossentropy(y_true=input_re1, y_pred=y_re1), axis=-1, keepdims=True)
    loss_re1 = tf.reduce_sum(loss_re1)

    loss_re2 = tf.reduce_sum(tf.keras.losses.binary_crossentropy(y_true=input_re2, y_pred=y_re2), axis=-1, keepdims=True)
    loss_re2 = tf.reduce_sum(loss_re2)
    loss = (loss_ner1 + loss_ner2) + (loss_re1 + loss_re2)

    return loss, (loss_ner1 + loss_ner2), (loss_re1 + loss_re2)

class Extra_result(object):
    def __init__(self, text):
        self.text = text
    def call(self):
        result = []
        token = np.zeros(len(self.text))
        text2id = Token(self.text)
        token[0:len(text2id)] = text2id
        Model_ner = model_Ner
        Model_er = model_Er
        ner1, ner2, out_lm = Model_ner(np.array([token], dtype=np.int32))
        subjects = self.extra_sujects(ner1, ner2)
        for i, key in enumerate(subjects):
            ids1 = key[1]
            ids2 = key[2]
            re1, re2 = Model_er(out_lm, np.array([ids1], dtype=np.int32), np.array([ids2], dtype=np.int32))
            relationship = self.extra_er(key[0], re1, re2)
            result.extend(relationship)
        print(subjects)
        print(result)
        return result

    def extra_sujects(self, ner_1, ner_2):
        subject = []
        ner_1, ner_2 = np.where(ner_1[0] > 0.5)[0], np.where(ner_2[0] > 0.5)[0]
        if len(ner_1) > 0:
            for i in ner_1:
                j = ner_2[ner_2 >= i]
                if len(j) > 0:
                    j = j[0]
                    _subject = self.text[i: j+1]
                    subject.append((_subject, i, j))
        return subject

    def extra_er(self, key, re1, re2):
        relationship = []
        o_re1, o_re2 = np.where(re1[0] > 0.5), np.where(re2[0] > 0.5)
        for _re1, c1 in zip(*o_re1):
            for _re2, c2 in zip(*o_re2):
                if _re1 <= _re2 and c1 == c2:
                    _object = self.text[_re1: _re2 + 1]
                    _predicate = id2predicate[c1]
                    relationship.append((key, _predicate, _object))
                    break
        return relationship

class Evaluate(object):
    def __init__(self):
        pass
    def reset(self,spo_list):
        xx = []
        for key in spo_list:
            xx.append((key[0], key[1], key[2]))
        return xx
    def evaluate(self, data):
        A, B, C = 1e-10, 1e-10, 1e-10
        for d in data[0:10]:
            extra_items = Extra_result(d['text'])
            R = set(extra_items.call())
            T = set(self.reset(d['spo_list']))
            A += len(R & T)
            B += len(R)
            C += len(T)
        return 2 * A / (B + C), A / B, A / C

model_Ner = Ner_model()
model_Er = ER_model()
optimizer = tf.keras.optimizers.Adam(learning_rate=lr)
checkpoint = tf.train.Checkpoint(optimizer=optimizer, model_Ner=model_Ner, model_Er=model_Er)
evaluate = Evaluate()
data_loader = data_loader()
best = 0.0

for epoch in range(num_epochs):
    print('Epoch:', epoch + 1)

    num_batchs = int(data_loader.num_train / batch_size) + 1
    for batch_index in range(num_batchs):
        input_x, input_ner1, input_ner2, input_re1, input_re2, position_s, position_e = data_loader.get_batch(batch_size)

        with tf.GradientTape() as tape:
            y_1, y_2, out_lstm = model_Ner(input_x) #预测ner
            y_re1, y_re2 = model_Er(out_lstm, position_s, position_e)
            loss, loss1, loss2 = loss_function(y_1, y_2, y_re1, y_re2, input_ner1, input_ner2, input_re1, input_re2)
            if (batch_index+1) % 500 == 0:
                print("batch %d: loss %f: loss1 %f: loss2 %f" % (batch_index+1, loss.numpy(), loss1.numpy(), loss2.numpy()))

        variables = (model_Ner.variables + model_Er.variables)
        grads = tape.gradient(loss, variables)
        optimizer.apply_gradients(grads_and_vars=zip(grads, variables))
    F, P, R = evaluate.evaluate(dev_data)
    print('测试集:', "F %f: P %f: R %f: " % (F, P, F))
    if round(F, 2) > best and round(F, 2) > 0.50:
        best = F
        print('saving_model')
        #model.save('./save/Entity_Relationshaip_version2.h5')
        checkpoint.save('./save/Entity_Relationship/version1_checkpoints.ckpt')


# -*- coding: utf-8 -*-
from django.db import models
from django.utils.translation import ugettext as _
from django.contrib.auth.models import User
from django.core.validators import MinValueValidator, MaxValueValidator
from django.dispatch import receiver
from django.db.models.signals import post_save

from main.helper.models import *
from main.helper.models import ModelFieldsAccessTypeMixin
from main.account.models import Account
from main.project.models import Project
from .collections import TaskCollectionsFabric
from .tasks import caching_all_rendered_tasks_items

class TaskFieldsAccessTypeMixin(ModelFieldsAccessTypeMixin):
    """
    Определение и получение прав на каждое поле задачи.
    """

    def get_field_desc_access_type(self, user):
        roles = self.get_user_roles(user)
        if roles & set([self.ROLE_AUTHOR, self.ROLE_MANAGER, self.ROLE_LEAD_PROGRAMMER, self.ROLE_SUPERUSER]):
            return self.FIELD_ACCESS_TYPE_FULL
        else:
            return self.FIELD_ACCESS_TYPE_VIEW

    def get_field_priority_access_type(self, user):
        return self.get_field_desc_access_type(user)

    def get_field_importance_access_type(self, user):
        return self.get_field_desc_access_type(user)

    def get_field_status_access_type(self, user):
        return self.get_field_desc_access_type(user)

    def get_field_performer_access_type(self, user):
        return self.get_field_desc_access_type(user)

    def get_field_lead_programmer_access_type(self, user):
        roles = self.get_user_roles(user)
        if roles & set([self.ROLE_AUTHOR, self.ROLE_MANAGER, self.ROLE_SUPERUSER]):
            return self.FIELD_ACCESS_TYPE_FULL
        else:
            return self.FIELD_ACCESS_TYPE_VIEW

    def get_field_tester_access_type(self, user):
        return self.get_field_performer_access_type(user)

    def get_field_manager_access_type(self, user):
        return self.get_field_lead_programmer_access_type(user)

    def get_field_client_access_type(self, user):
        return self.get_field_lead_programmer_access_type(user)

class TaskActionsMixin(object):
    ACTION_SEND_TO_PERFORMER            = 'send_to_performer'
    ACTION_REJECT_PERFORM               = 'reject_perform'
    ACTION_ADD_TO_PERFORMANCE_QUEUE     = 'add_to_performance_queue'
    ACTION_START_PERFORMANCE            = 'start_performance'
    ACTION_PAUSE_PERFORMANCE            = 'pause_performance'
    ACTION_MARK_AS_PERFORMED            = 'mark_as_performed'
    ACTION_SEND_TO_LEAD_PROGRAMMER      = 'send_to_lead_programmer'
    ACTION_ADD_TO_CODE_REVIEW_QUEUE     = 'add_to_code_review_queue'
    ACTION_START_CODE_REVIEW            = 'start_code_review'
    ACTION_SEND_TO_TESTING              = 'send_to_testing'
    ACTION_REJECT_TEST                  = 'reject_test'
    ACTION_ADD_TO_TESTING_QUEUE         = 'add_to_testing_queue'
    ACTION_START_TESTING                = 'start_testing'
    ACTION_ACCEPT_WORK_BY_TESTER        = 'accept_work_by_tester'
    ACTION_SEND_TO_CLIENT               = 'send_to_client'
    ACTION_ADD_TO_CLIENT_CHECKING_QUEUE = 'add_to_client_checking_queue'
    ACTION_START_CLIENT_CHECKING        = 'start_client_checking'
    ACTION_SEND_TO_REWORK               = 'send_to_rework'
    ACTION_ACCEPT_WORK_BY_CLIENT        = 'accept_work_by_client'
    ACTION_CLOSE_TASK                   = 'close_task'

    ACTIONS = {
        ACTION_SEND_TO_PERFORMER            : u'Отправить исполнителю',
        ACTION_REJECT_PERFORM               : u'Отказаться выполнять',
        ACTION_ADD_TO_PERFORMANCE_QUEUE     : u'В очередь',
        ACTION_START_PERFORMANCE            : u'Начать выполнение',
        ACTION_PAUSE_PERFORMANCE            : u'Пауза выполнения',
        ACTION_MARK_AS_PERFORMED            : u'Пометить как выполненное',
        ACTION_SEND_TO_LEAD_PROGRAMMER      : u'Отправить тимлиду',
        ACTION_ADD_TO_CODE_REVIEW_QUEUE     : u'В очередь',
        ACTION_START_CODE_REVIEW            : u'Начать проверку',
        ACTION_SEND_TO_TESTING              : u'Отправить на тестирование',
        ACTION_REJECT_TEST                  : u'Отказаться тестировать',
        ACTION_ADD_TO_TESTING_QUEUE         : u'В очередь',
        ACTION_START_TESTING                : u'Начать тестирование',
        ACTION_ACCEPT_WORK_BY_TESTER        : u'Ошибок не найдено',
        ACTION_SEND_TO_CLIENT               : u'Отправить заказчику',
        ACTION_ADD_TO_CLIENT_CHECKING_QUEUE : u'В очередь',
        ACTION_START_CLIENT_CHECKING        : u'Начать проверку',
        ACTION_SEND_TO_REWORK               : u'Отправить на доработку',
        ACTION_ACCEPT_WORK_BY_CLIENT        : u'Принять работу',
        ACTION_CLOSE_TASK                   : u'Завершить задачу',
    }

    def get_available_actions(self, user):
        """
        Возвращает список доступных действий,
        которые можно выполнить над задачей при текущих значениях полей
        и для указанного пользователя.
        """
        actions = {}

        roles = self.get_user_roles(user)

        for name, title in self.ACTIONS.items():
            method_name = 'has_action_{0}'.format(name)
            method = getattr(self, method_name, None)
            if callable(method):
                if method(user, roles):
                    actions.update({
                        name: title,
                    })

        return actions

    def has_action_send_to_performer(self, user, roles):
        if self.ROLE_LEAD_PROGRAMMER in roles and self.status == self.STATUS_CODE_REVIEW:
            return True
        if self.ROLE_TESTER in roles and self.status == self.STATUS_TESTING:
            return True
        if self.ROLE_MANAGER in roles and self.status in (
            self.STATUS_PERFORMER_REJECTED,
            self.STATUS_WENT_TO_TESTING,
            self.STATUS_TESTER_REJECTED,
            self.STATUS_WAIT_TESTING,
            self.STATUS_TESTING,
            self.STATUS_TESTER_ACCEPTED,

        ):
            return True

    def has_action_reject_perform(self, user, roles):
        if self.ROLE_PERFORMER in roles \
        and self.status in (self.STATUS_WENT_TO_PERFORMER, self.STATUS_PERFORMANCE):
            return True

    def has_action_add_to_performance_queue(self, user, roles):
        if self.ROLE_PERFORMER in roles and self.status == self.STATUS_WENT_TO_PERFORMER:
            return True

    def has_action_start_performance(self, user, roles):
        if self.ROLE_PERFORMER in roles \
        and self.status in (
                self.STATUS_WENT_TO_PERFORMER,
                self.STATUS_WAIT_PERFORMANCE,
                self.STATUS_PERFORMANCE_PAUSE,
        ):
            return True

    def has_action_pause_performance(self, user, roles):
        if self.ROLE_PERFORMER in roles \
        and self.status in (
                self.STATUS_PERFORMANCE,
        ):
            return True

    def has_action_mark_as_performed(self, user, roles):
        if self.ROLE_PERFORMER in roles and self.status == self.STATUS_PERFORMANCE:
            return True

    def has_action_send_to_lead_programmer(self, user, roles):
        if self.lead_programmer:
            if self.ROLE_PERFORMER in roles and self.status == self.STATUS_PERFORMANCE:
                return True

    def has_action_add_to_code_review_queue(self, user, roles):
        if self.ROLE_LEAD_PROGRAMMER in roles and self.status == self.STATUS_WENT_TO_LEAD_PROGRAMMER:
            return True

    def has_action_start_code_review(self, user, roles):
        if self.ROLE_LEAD_PROGRAMMER in roles \
        and self.status in (self.STATUS_WENT_TO_LEAD_PROGRAMMER, self.STATUS_WAIT_CODE_REVIEW):
            return True

    def has_action_send_to_testing(self, user, roles):
        if self.lead_programmer:
            if self.ROLE_LEAD_PROGRAMMER in roles and self.status == self.STATUS_CODE_REVIEW:
                return True
        elif self.tester:
            if self.ROLE_PERFORMER in roles and self.status == self.STATUS_PERFORMED:
                return True

    def has_action_reject_test(self, user, roles):
        if self.ROLE_TESTER in roles and self.status == self.STATUS_TESTING:
            return True

    def has_action_add_to_testing_queue(self, user, roles):
        if self.ROLE_TESTER in roles and self.status == self.STATUS_WENT_TO_TESTING:
            return True

    def has_action_start_testing(self, user, roles):
        if self.ROLE_TESTER in roles and self.status in (self.STATUS_WENT_TO_TESTING, self.STATUS_WAIT_TESTING):
            return True

    def has_action_accept_work_by_tester(self, user, roles):
        if self.ROLE_TESTER in roles and self.status == self.STATUS_TESTING:
            return True

    def has_action_send_to_client(self, user, roles):
        if self.ROLE_MANAGER in roles and self.client:
            if self.tester:
                if self.status == self.STATUS_TESTER_ACCEPTED:
                    return True
            elif self.lead_programmer:
                if self.status == self.STATUS_LEAD_PROGRAMMER_ACCEPTED:
                    return True
            else:
                if self.status == self.STATUS_PERFORMED:
                    return True

    def has_action_add_to_client_checking_queue(self, user, roles):
        if self.ROLE_CLIENT in roles and self.status == self.STATUS_WENT_TO_CLIENT:
            return True

    def has_action_start_client_checking(self, user, roles):
        if self.ROLE_CLIENT in roles and self.status in (self.STATUS_WAIT_CLIENT_CHECKING, self.STATUS_WENT_TO_CLIENT):
            return True

    def has_action_send_to_rework(self, user, roles):
        if self.ROLE_CLIENT in roles and self.status == self.STATUS_CLIENT_CHECKING:
            return True

    def has_action_accept_work_by_client(self, user, roles):
        if self.ROLE_CLIENT in roles and self.status == self.STATUS_CLIENT_CHECKING:
            return True

    def has_action_close_task(self, user, roles):
        if self.manager:
            if self.ROLE_MANAGER in roles:
                return True
        else:
            if self.ROLE_AUTHOR in roles:
                return True

    def run_action(self, action, user):
        """
        Выполняет указанное действие над объектом модели.
        """
        method_name = 'action_{0}'.format(action)
        method = getattr(self, method_name, None)
        if callable(method):
            return method(user)

    def action_send_to_performer(self, user):
        self.status = self.STATUS_WENT_TO_PERFORMER
        self.save()

    def action_reject_perform(self, user):
        self.status = self.STATUS_PERFORMER_REJECTED
        self.save()

    def action_add_to_performance_queue(self, user):
        self.status = self.STATUS_WAIT_PERFORMANCE
        self.save()

    def action_start_performance(self, user):
        self.status = self.STATUS_PERFORMANCE
        self.save()

    def action_pause_performance(self, user):
        self.status = self.STATUS_PERFORMANCE_PAUSE
        self.save()

    def action_mark_as_performed(self, user):
        """
        Помечает задачу как выполненную исполнителем.
        """
        if self.lead_programmer:
            self.status = self.STATUS_WENT_TO_LEAD_PROGRAMMER
        elif self.tester:
            self.status = self.STATUS_WENT_TO_TESTING
        else:
            self.status = self.STATUS_PERFORMED
        self.save()

    def action_add_to_code_review_queue(self, user):
        self.status = self.STATUS_WAIT_CODE_REVIEW
        self.save()

    def action_start_code_review(self, user):
        self.status = self.STATUS_CODE_REVIEW
        self.save()

    def action_send_to_testing(self, user):
        self.status = self.STATUS_WENT_TO_TESTING
        self.save()

    def action_reject_test(self, user):
        self.status = self.STATUS_TESTER_REJECTED
        self.save()

    def action_add_to_testing_queue(self, user):
        self.status = self.STATUS_WAIT_TESTING
        self.save()

    def action_start_testing(self, user):
        self.status = self.STATUS_TESTING
        self.save()

    def action_accept_work_by_tester(self, user):
        self.status = self.STATUS_TESTER_ACCEPTED
        self.save()

    def action_send_to_client(self, user):
        self.status = self.STATUS_WENT_TO_CLIENT
        self.save()

    def action_add_to_client_checking_queue(self, user):
        self.status = self.STATUS_WAIT_CLIENT_CHECKING
        self.save()

    def action_start_client_checking(self, user):
        self.status = self.STATUS_CLIENT_CHECKING
        self.save()

    def action_send_to_rework(self, user):
        self.status = self.STATUS_CLIENT_REJECTED
        self.save()

    def action_accept_work_by_client(self, user):
        self.status = self.STATUS_CLIENT_ACCEPTED
        self.save()

    def action_close_task(self, user):
        self.status = self.STATUS_CLOSED
        self.save()

class Task(EntityBaseFields, TitleField, DescField,
           TaskFieldsAccessTypeMixin, TaskActionsMixin):
    """
    Задача.
    """

    PRIORITY_CHOICES = {
        -2: u'Совсем не срочно',
        -1: u'Не срочно',
        0: u'Обычно',
        1: u'Срочно',
        2: u'Очень срочно',
    }

    IMPORTANCE_CHOICES = {
        -2: u'Совсем не важно',
        -1: u'Не важно',
        0: u'Обычно',
        1: u'Важно',
        2: u'Очень важно',
    }

    # использую константы для всех статусов, чтобы свести к минимуму ошибки
    # опечаток использования статусов
    STATUS_WENT_TO_PERFORMER  = 11
    STATUS_PERFORMER_REJECTED = 12
    STATUS_WAIT_PERFORMANCE   = 13
    STATUS_PERFORMANCE        = 14
    STATUS_PERFORMANCE_PAUSE  = 15
    STATUS_PERFORMED          = 16

    STATUS_WENT_TO_LEAD_PROGRAMMER  = 21
    STATUS_WAIT_CODE_REVIEW         = 22
    STATUS_CODE_REVIEW              = 23
    STATUS_LEAD_PROGRAMMER_ACCEPTED = 24

    STATUS_WENT_TO_TESTING = 31
    STATUS_TESTER_REJECTED = 32
    STATUS_WAIT_TESTING    = 33
    STATUS_TESTING         = 34
    STATUS_TESTER_ACCEPTED = 35

    STATUS_WENT_TO_CLIENT       = 41
    STATUS_WAIT_CLIENT_CHECKING = 42
    STATUS_CLIENT_CHECKING      = 43
    STATUS_CLIENT_ACCEPTED      = 44
    STATUS_CLIENT_REJECTED      = 45
    STATUS_CLOSED               = 46

    STATUSES = {
        STATUS_WENT_TO_PERFORMER  : u'Отправлена исполнителю',
        STATUS_PERFORMER_REJECTED : u'Отказ выполнять',
        STATUS_WAIT_PERFORMANCE   : u'Ожидает выполнения',
        STATUS_PERFORMANCE        : u'Выполнение',
        STATUS_PERFORMANCE_PAUSE  : u'Пауза',
        STATUS_PERFORMED          : u'Решено',

        STATUS_WENT_TO_LEAD_PROGRAMMER : u'Отправлена тимлиду',
        STATUS_WAIT_CODE_REVIEW        : u'Ожидает проверки кода',
        STATUS_CODE_REVIEW             : u'Проверка кода',

        STATUS_WENT_TO_TESTING : u'Отправлена на тестирование',
        STATUS_TESTER_REJECTED : u'Отказ тестировать',
        STATUS_WAIT_TESTING    : u'Ожидает тестирования',
        STATUS_TESTING         : u'Тестирование',
        STATUS_TESTER_ACCEPTED : u'Успешно протестирована',

        STATUS_WENT_TO_CLIENT       : u'Отправлена заказчику',
        STATUS_WAIT_CLIENT_CHECKING : u'Ожидает проверки заказчиком',
        STATUS_CLIENT_CHECKING      : u'Проверка заказчиком',
        STATUS_CLIENT_REJECTED      : u'Не принята заказчиком',
        STATUS_CLIENT_ACCEPTED      : u'Принята заказчиком',
        STATUS_CLOSED               : u'Завершена',
    }

    # роли пользователя в данной задаче
    ROLE_SUPERUSER       = 'superuser'
    ROLE_AUTHOR          = 'author'
    ROLE_PERFORMER       = 'performer'
    ROLE_LEAD_PROGRAMMER = 'lead_programmer'
    ROLE_TESTER          = 'tester'
    ROLE_MANAGER         = 'manager'
    ROLE_CLIENT          = 'client'

    priority   = models.IntegerField(choices = PRIORITY_CHOICES.items(), verbose_name = u'Срочность', default = 0)
    importance = models.IntegerField(choices = IMPORTANCE_CHOICES.items(), verbose_name = u'Важность', default = 0)
    author     = models.ForeignKey('account.Account', verbose_name = u'Автор', related_name = 'task_author')

    status = models.IntegerField(choices = STATUSES.items(), verbose_name = u'Статус', default = STATUS_WENT_TO_PERFORMER)

    performer       = models.ForeignKey('account.Account', verbose_name = u'Исполнитель', related_name = 'task_performer', blank = True, null = True)
    lead_programmer = models.ForeignKey('account.Account', verbose_name = u'Ведущий разработчик', related_name = 'task_lead_programmer', blank = True, null = True)
    tester          = models.ForeignKey('account.Account', verbose_name = u'Тестировщик', related_name = 'task_tester', blank = True, null = True)
    manager         = models.ForeignKey('account.Account', verbose_name = u'Менеджер', related_name = 'task_manager', blank = True, null = True)
    client          = models.ForeignKey('account.Account', verbose_name = u'Клиент', related_name = 'task_client', blank = True, null = True)

    project    = models.ForeignKey('project.Project', verbose_name = u'Проект', related_name = 'task_project', blank = True, null = True)
    specification = models.ForeignKey('specification.Specification', verbose_name = u'Глава ТЗ', related_name = 'task_specification', blank = True, null = True)
    release = models.ForeignKey('release.Release', verbose_name = u'Релиз', related_name = 'task_release', blank = True, null = True)

    # objects = models.Manager()

    class Meta():
        verbose_name = 'Task'
        verbose_name_plural = 'Tasks'

    def get_user_roles(self, user):
        """
        Возвращает список ролей указанного пользователя в данной задаче
        """
        roles = []
        if user.is_superuser:
            roles.append(self.ROLE_SUPERUSER)

        if user.pk == getattr(self.performer, 'pk', None):
            roles.append(self.ROLE_PERFORMER)

        if user.pk == getattr(self.lead_programmer, 'pk', None):
            roles.append(self.ROLE_LEAD_PROGRAMMER)

        if user.pk == getattr(self.tester, 'pk', None):
            roles.append(self.ROLE_TESTER)

        if user.pk == getattr(self.manager, 'pk', None):
            roles.append(self.ROLE_MANAGER)

        if user.pk == getattr(self.client, 'pk', None):
            roles.append(self.ROLE_CLIENT)

        return set(roles)

    def get_field_available_choices(self, user, field_name):
        """
        Возвращает словарь допустимых значений
        указанному пользователю для указанного поля.
        """
        method_name = 'get_{0}_field_available_choices'.format(field_name)
        method = getattr(self, method_name)
        if callable(method):
            return method(user)

    def get_members(self, only_ids=False):
        """
        Возвращает всех пользователей, которые учавствуют в работе над задачей.
        """
        members = []
        if self.performer:
            members.append(self.performer)
        if self.lead_programmer:
            members.append(self.lead_programmer)
        if self.tester:
            members.append(self.tester)
        if self.manager:
            members.append(self.manager)
        if self.client:
            members.append(self.client)

        if only_ids:
            return [u.id for u in members]
        else:
            return members

@receiver(post_save, sender=Task)
def task_post_save(sender, instance, *args, **kwargs):
    # запускаем ассинхронно через celery кэширование всего,
    # что связано с сохраненной задачей
    caching_all_rendered_tasks_items.delay(instance)

    # collections_fabric = TaskCollectionsFabric(instance)
    # # добавляем задачу в коллекции, которым подходит данная задача по свойствам
    # for c in collections_fabric.get_appropriate_collections():
    #     c.add_item(instance.pk)
    # # исключаем задачу из коллекций, которым данная задача не подходит по свойствам
    # for c in collections_fabric.get_inappropriate_collections():
    #     c.delete_item(instance.pk)


# library import
import time
import os
import matplotlib.pyplot as plt
import pandas as pd
import plotly.graph_objs as go
import numpy as np
import csv
import random

from pandas import merge
from scipy import stats
from collections import Counter
from plotly.offline import init_notebook_mode, iplot
from sklearn.cluster import KMeans
from sklearn.utils import shuffle
from sklearn.decomposition import PCA

init_notebook_mode(connected=True)

# 2D 그래프로 표현
def gettrace(patient_clusters, cluster_num):
    r = random.randrange(1,255)
    g = random.randrange(1,255)
    b = random.randrange(1,255)
    return go.Scatter(x = patient_clusters[patient_clusters.cluster == cluster_num]['x'],
                   y = patient_clusters[patient_clusters.cluster == cluster_num]['y'],
                   name = "Cluster {}".format(cluster_num+1),
                   mode = "markers",
                   marker = dict(size = 10,
                                 color = "rgba({0}, {1}, {2}, 0.5)".format(r, g, b),
                                 line = dict(width = 1, color = "rgb(0,0,0)")))

# 최적의 클러스터 개수를 추출하기 위한 함수
def elbow(X):
    sse = []
    for i in range(1, 10):
        km = KMeans(n_clusters=i, init='k-means++', random_state=0)
        km.fit(X)
        sse.append(km.inertia_)
        print (km.inertia_)
    plt.plot(range(1, 10), sse, marker='o')
    plt.xlabel('cluster count')
    plt.ylabel('SSE')
    plt.show()

src_dic = {}
dst_dic = {}

loopcnt = 0

f = open('data/netflow_dump.csv', 'r', encoding='utf-8')
rdr = csv.reader(f)

for line in rdr:
    src = line[0]
    dst = line[1]

    src_dic.setdefault(src, set())
    src_dic[src].add(dst)
f.close()

dst_li = [len(x) for x in src_dic.values()]

# 고유한 DST ADDR 수집
id_dst_dic = {}
for dst_set_li in src_dic.values():
    for x in dst_set_li:
        if x not in id_dst_dic:
            id_dst_dic.setdefault(x, len(id_dst_dic))

print("# of left dst:%d" % (len(id_dst_dic)))

id_src_dic = {}

# DST ADDR 개수
all_dst_count = len(id_dst_dic)

produc_columns = [x for x in id_dst_dic.values()]
produc_columns.sort()
produc_columns.insert(0, 'srcip')

df = pd.DataFrame(columns=produc_columns)

for srcip, dsts in src_dic.items():
    src_dst_vec = [0] * all_dst_count

    id_src_dic[len(id_src_dic)] = srcip

    for dstip in dsts:
        src_dst_vec[id_dst_dic[dstip]] = 1

    # 첫번째 columns를 src ip로한 dataframe 생성
    src_dst_vec.insert(0, srcip)
    df.loc[len(id_src_dic)] = [n for n in src_dst_vec]

# 데이터를 랜덤으로 섞는다
df = shuffle(df)

# 데이터의 70%만 추출
tran_lenght = int(len(df) * 0.7)

# 학습 데이터와 테스트 데이터로 분리
train_data = df[df.columns[1:]][:tran_lenght+1]
test_data = df[df.columns[1:]][tran_lenght:]

print ("70% data count: ", tran_lenght)

# 최적의 클러스터 확인
elbow(train_data)

cols = df.columns[1:]

clusternum = 4

# 데이터 학습
kmeans = KMeans(n_clusters = clusternum)
kmeans.fit(train_data)

# 테스트 데이터 클러스터
test_clusters = df[:][tran_lenght:]
test_clusters["cluster"] = kmeans.predict(test_data)
test_clusters.tail()

# 좌표 데이터 추출
pca = PCA(n_components = 2)
test_clusters['x'] = pca.fit_transform(test_clusters[cols])[:, 0]
test_clusters['y'] = pca.fit_transform(test_clusters[cols])[:, 1]
test_clusters.tail()

# dataframe 재정의(ip, cluster, x, y)
patient_clusters = test_clusters[['srcip', 'cluster', 'x', 'y']]
patient_clusters.tail()

# 2D 그래프로 시각화
data = []
for idx in range(clusternum):
    data.append(gettrace(patient_clusters, idx))

iplot(data)


import argparse

parser = argparse.ArgumentParser(description='Process some integers.')
parser.add_argument('x')
parser.add_argument('y')

args = parser.parse_args()
data = vars(args)

x = data.get('x')
y = data.get('y')

def hello(x,y):
    z = None
    try:
        z = x / y
    except ValueError as err:
        print(f'Error: ValueError {err}')
    except ZeroDivisionError as err:
        print(f'Error: ZeroDivisionError: {err}')
    except Exception as err:
        print(f'Exception {err.__class__.__name__}: {err}')
    finally:
        print('here')
        print(f'x={x} y={y} z={z}')

hello(x,y)


""" Automated tests for HW09_Student_Repository.py """

import unittest
import os
from Student_Repository_Diaeddin_Motan import Repository, Student, Instructor, Major
from utilities import file_reader
import sqlite3

class TestRepository(unittest.TestCase):
    def setUp(self):
        self.test_path = '/Users/dmotan/Desktop/Master/SSW-810/week11'
        self.repo = Repository(self.test_path, False)

    # def test_Student_attributes(self):
    #     """ Verify that a specific student is set up properly """
    #     expected = {
    #         '10103': ('10103', 'Baldwin, C', ['CS 501', 'SSW 564', 'SSW 567', 'SSW 687'], ['SSW 540', 'SSW 555'], [], 3.44),
    #         '10115': ('10115', 'Wyatt, X', ['CS 545', 'SSW 564', 'SSW 567', 'SSW 687'], ['SSW 540', 'SSW 555'], [], 3.81),
    #         '10172': ('10172', 'Forbes, I', ['SSW 555', 'SSW 567'], ['SSW 540', 'SSW 564'], ['CS 501', 'CS 513', 'CS 545'], 3.88),
    #         '10175': ('10175', 'Erickson, D', ['SSW 564', 'SSW 567', 'SSW 687'], ['SSW 540', 'SSW 555'], ['CS 501', 'CS 513', 'CS 545'], 3.58),
    #         '10183': ('10183', 'Chapman, O', ['SSW 689'], ['SSW 540', 'SSW 555', 'SSW 564', 'SSW 567'], ['CS 501', 'CS 513', 'CS 545'], 4.0),
    #         '11399': ('11399', 'Cordova, I', ['SSW 540'], ['SYS 612', 'SYS 671', 'SYS 800'], [], 3.0),
    #         '11461': ('11461', 'Wright, U', ['SYS 611', 'SYS 750', 'SYS 800'], ['SYS 612', 'SYS 671'], ['SSW 540', 'SSW 565', 'SSW 810'], 3.92),
    #         '11658': ('11658', 'Kelly, P', ['SSW 540'], ['SYS 612', 'SYS 671', 'SYS 800'], [], 0.0),
    #         '11714': ('11714', 'Morton, A', ['SYS 611', 'SYS 645'], ['SYS 612', 'SYS 671', 'SYS 800'], ['SSW 540', 'SSW 565', 'SSW 810'], 3.0),
    #         '11788': ('11788', 'Fuller, E', ['SSW 540'], ['SYS 612', 'SYS 671', 'SYS 800'], [], 4.0)
    #     }

    #     calculated = {cwid: student.pt_row()
    #                   for cwid, student in self.repo._students.items()}

    #     self.assertEqual(expected, calculated)

    # def test_Instructor_attributes(self):
    #     """ Verify that a specific instructor is set up properly """
    #     expected = {
    #         ('98765', 'Einstein, A', 'SFEN', 'SSW 567', 4),
    #         ('98765', 'Einstein, A', 'SFEN', 'SSW 540', 3),
    #         ('98764', 'Feynman, R', 'SFEN', 'SSW 564', 3),
    #         ('98764', 'Feynman, R', 'SFEN', 'SSW 687', 3),
    #         ('98764', 'Feynman, R', 'SFEN', 'CS 501', 1),
    #         ('98764', 'Feynman, R', 'SFEN', 'CS 545', 1),
    #         ('98763', 'Newton, I', 'SFEN', 'SSW 555', 1),
    #         ('98763', 'Newton, I', 'SFEN', 'SSW 689', 1),
    #         ('98760', 'Darwin, C', 'SYEN', 'SYS 800', 1),
    #         ('98760', 'Darwin, C', 'SYEN', 'SYS 750', 1),
    #         ('98760', 'Darwin, C', 'SYEN', 'SYS 611', 2),
    #         ('98760', 'Darwin, C', 'SYEN', 'SYS 645', 1)
    #     }

    #     calculated = {tuple(detail) for instructor in self.repo._instructors.values(
    #     ) for detail in instructor.pt_row()}

    #     self.assertEqual(expected, calculated)

    # def test_Major_attributes(self):
    #     """ Verify that a specific major is set up properly """
    #     expected = {
    #         'SFEN': ('SFEN', ['SSW 540', 'SSW 555', 'SSW 564', 'SSW 567'], ['CS 501', 'CS 513', 'CS 545']),
    #         'SYEN': ('SYEN', ['SYS 612', 'SYS 671', 'SYS 800'], ['SSW 540', 'SSW 565', 'SSW 810']),
    #     }

    #     calculated = {name: major.pt_row()
    #                   for name, major in self.repo._majors.items()}

    #     self.assertEqual(expected, calculated)

    def test_student_grade_summary(self):
        """ Verify that a specific major is set up properly """
        DB_FILE: str = "/Users/dmotan/Desktop/Master/SSW-810/week11/810_student_repo.db"
        db: sqlite3.Connection = sqlite3.connect(DB_FILE)

        expected = {
            ('Bezos, J', '10115', 'SSW 810', 'A', 'Rowland, J'),
            ('Bezos, J', '10115', 'CS 546', 'F', 'Hawking, S'),
            ('Gates, B', '11714', 'SSW 810', 'B-', 'Rowland, J'),
            ('Gates, B', '11714', 'CS 546', 'A', 'Cohen, R'),
            ('Gates, B', '11714', 'CS 570', 'A-', 'Hawking, S'),
            ('Jobs, S', '10103', 'SSW 810', 'A-', 'Rowland, J'),
            ('Jobs, S', '10103', 'CS 501', 'B', 'Hawking, S'),
            ('Musk, E', '10183', 'SSW 555', 'A', 'Rowland, J'),
            ('Musk, E', '10183', 'SSW 810', 'A', 'Rowland, J')
        }

        calculated = {row
                      for row in db.execute("select s.Name, s.CWID, g.Course, g.Grade, i.Name from grades g join students s on s.CWID=g.StudentCWID join instructors i on g.InstructorCWID=i.CWID order by s.Name asc")}

        self.assertEqual(expected, calculated)

if __name__ == '__main__':
    unittest.main(exit=False, verbosity=2)


from asyncio import sleep, exceptions
import json

from fastapi import APIRouter, Request, WebSocket
from fastapi.responses import HTMLResponse
from redis.asyncio import Redis
from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError

from controller import DataController
from settings import settings
from templates.template_processor import template_processor

router = APIRouter()

@router.get("/", response_class=HTMLResponse)
async def root(request: Request):
    ticker_names = await DataController.get_ticker_names()
    return template_processor.TemplateResponse(
        "index.html",
        {
            "request": request,
            "ticker_names": sorted(ticker_names, key=lambda item: int(item.split("_")[1])),
        },
    )

@router.get("/ticker_entries/{ticker_name}")
async def get_entries(ticker_name: str):
    return await DataController.get_all_ticker_entries(ticker_name)

@router.websocket("/realtime_data/{ticker_name}")
async def get_realtime(ticker_name: str, websocket: WebSocket):
    await websocket.accept()
    redis_client = Redis(host=settings.REDIS_HOST, port=settings.REDIS_PORT, db=settings.REDIS_DB)
    channel = redis_client.pubsub()
    await channel.subscribe(ticker_name)
    while True:
        try:
            msg = await channel.get_message(ignore_subscribe_messages=True)
            if msg is not None:
                await websocket.send_json(json.loads(msg["data"]))
            await sleep(0.01)
        except (ConnectionClosedOK, ConnectionClosedError):
            return
        except exceptions.CancelledError:
            await websocket.close()
            return


'''
Problem Solving Baekjoon 7569
Author: Injun Son
Date: October 18, 2020
'''

from collections import deque
import sys
import math
import copy

width, height, depth = map(int, input().split())
maps = []
starts = []
no_zero = True

def bfs(starts, maps, depth, height, width):
    #가로, 세로, 높이로 각각 이동
    dirs = [(1,0,0), (-1, 0, 0), (0, 1, 0), (0, -1, 0), (0, 0, 1), (0, 0, -1)]
    queue = deque()
    queue.extend(starts)
    while queue:
        cd, ch, cw, cnt = queue.popleft()
        for dd, dh, dw in dirs:
            nd, nh, nw = cd+dd, ch+dh, cw+dw
            if 0<=nh<height and 0<=nd<depth and 0<=nw<width and maps[nd][nh][nw]==0:
                maps[nd][nh][nw] = 1
                queue.append((nd, nh, nw, cnt+1))
    return cnt

#토마토가 bfs후 다 익었는지 아닌지 체크
def check(maps, depth, height, width):
    for d in range(depth):
        for h in range(height):
            for w in range(width):
                if maps[d][h][w] == 0:
                    return -1
    return 0

for d in range(depth):
    temp = []
    for h in range(height):
        a = list(map(int, input().split()))
        for w in range(width):
            if a[w]==0:
                no_zero = False
            if a[w] == 1:
                starts.append((d, h, w, 0))
        temp.append(copy.deepcopy(a))
    maps.append(copy.deepcopy(temp))

#처음부터 0이 하나도 없을 경우 더 계산할 필요가 없다
if no_zero:
    print(0)
    exit()

count = bfs(starts, maps, depth, height, width)

if check(maps, depth, height, width) != 0:
    print(-1)
else:
    print(count)


from __future__ import division
import pygame
import sys
import math
pygame.init()
WIDTH = 800
HEIGHT = 600
windowSurface = pygame.display.set_mode((WIDTH, HEIGHT))
import objects
import drawers
import misc

""" baseInfinity.py
by Eric J.Parfitt (ejparfitt@gmail.com)

This program lets one arrange certain tiles together which emulate a
rule 110 cellular automaton.

Version: 1.0 alpha
"""

FPS = 60
clock = pygame.time.Clock()

# The speed with which the display resizes
RESIZE_FACT = .9
# Factors for determining maximum and minimum scale when zooming in or
# out
MAX_SCALE = 1
MIN_SCALE = .005

# Tiles keeps track of tiles, with tiles at the top layer at the end of
# the list, and tiles at the bottom at the beginning of the list
tiles = []

# isSnapped refers to whether or not two groups of tiles currently
# appear snaped together
isScrollUp = isScrollDown = isDrag = isMouseDown = isLDown = isRDown = \
        isSnapped = False
# gridRes refers to the resolution of a grid on which tiles are placed,
# which increases when zoomed in and decreases when zoomed out
oldMouseLoc = gridRes = None
# selectedTile is the tile which is currently being clicked on.
# snadTile is the first tile from one moving group to attempt to snap to
# a tile in another group.  snadSide and adjSide are the sides which are
# going to snap together, snapd being the moving one, and adj being the
# stationary one.  snapdTile keeps track of one of the tiles which could
# snap to another one if the user unclicks
selectedTile = snapdTile = snapdSide = adjSide = None
selectedTiles = []
# sidesToSnap keeps track of tiles which can potentially be merged
# into one snapped together group if the mouse is unclicked
sidesToSnap = []
myCanvas = objects.Canvas(tiles, windowSurface)
# Sets up a grid which is used to only update tiles near the tiles
# currently being modified.
grid, gridRes = misc.setGrid(myCanvas, tiles)
# palletBack is just a white rectangle which is drawn behind the
# tile pallet, which has all the tiles which can be chosen from
palletBack = misc.resizePalletBack(myCanvas)
# Draws the tile pallet, along with any tiles specified by the variable
# "tiles"
drawers.initDraw(myCanvas, tiles, palletBack, windowSurface)
# Run loop
while(True):
    isClick = isUnClick = False
    button = None
    # Check for mouse clicks and unclicks
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()
        elif event.type == pygame.MOUSEBUTTONDOWN:
            if event.button == 1 or event.button == 3:
                button = event.button
                isClick = True
            if event.button == 4:
                isScrollUp = True
                # Changes Canvas.scale to decrease the size of the scene
                newScale = objects.Canvas.scale / RESIZE_FACT
                # Limits maximum size
                if newScale <= MAX_SCALE:
                    objects.Canvas.scale = newScale
            if event.button == 5:
                isScrollDown = True
                newScale = objects.Canvas.scale * RESIZE_FACT
                # Limits minimum size
                if newScale >= MIN_SCALE:
                    objects.Canvas.scale = newScale
        elif event.type == pygame.MOUSEBUTTONUP:
            isUnClick = True
    # Get mouse location
    mouseLoc = pygame.mouse.get_pos()
    # Updates the scene
    selectedTiles, grid, gridRes, isDrag, selectedTile, \
            palletBack, isSnapped, sidesToSnap, snapdTile, \
            snapdSide, adjSide, oldMouseLoc = misc.update(myCanvas, tiles,
            selectedTiles, mouseLoc, button, isUnClick, isClick, isScrollDown,
            isScrollUp, grid, gridRes, isDrag, selectedTile,
            palletBack, isSnapped, sidesToSnap, snapdTile,
            snapdSide, adjSide, clock, FPS, oldMouseLoc, windowSurface)
    isScrollDown = isScrollUp = False