import unittest2 as unittest

from plone.app.testing import TEST_USER_ID, setRoles
from Products.CMFPlone.utils import getToolByName

from collective.teamwork.content.interfaces import PROJECT_TYPE
from collective.teamwork.content.interfaces import WORKSPACE_TYPE
from layers import DEFAULT_PROFILE_TESTING

class DefaultProfileTest(unittest.TestCase):
    """Test default profile's installed configuration settings"""

    THEME = 'Sunburst Theme'

    layer = DEFAULT_PROFILE_TESTING

    def setUp(self):
        self.portal = self.layer['portal']
        self.wftool = getToolByName(self.portal, 'portal_workflow')
        setRoles(self.portal, TEST_USER_ID, ['Manager'])

    def _product_fti_names(self):
        """ test types used by collective.teamwork """
        return (PROJECT_TYPE, WORKSPACE_TYPE)

    def test_browserlayer(self):
        """Test product layer interfaces are registered for site"""
        from collective.teamwork.interfaces import ITeamworkProductLayer
        from upiqsite.teamwork.interfaces import IUPIQTeamworkSiteProductLayer
        from plone.browserlayer.utils import registered_layers
        self.assertTrue(ITeamworkProductLayer in registered_layers())
        self.assertTrue(IUPIQTeamworkSiteProductLayer in registered_layers())

    def test_ftis(self):
        types_tool = getToolByName(self.portal, 'portal_types')
        typenames = types_tool.objectIds()
        for name in self._product_fti_names():
            self.assertTrue(name in typenames)
        assert types_tool.getTypeInfo(PROJECT_TYPE).Title() == 'Project'
        assert types_tool.getTypeInfo(WORKSPACE_TYPE).Title() == 'Team'

    def test_skin_layer(self):
        names = ('main_template', 'upiqsite.teamwork.txt')
        tool = self.portal['portal_skins']
        self.assertTrue('upiqsite_teamwork' in tool)
        skin = tool.getSkin(self.THEME)
        path = tool.getSkinPath(self.THEME).split(',')
        # check order in path:
        self.assertEqual(path[0], 'custom')
        self.assertEqual(path[1], 'upiqsite_teamwork')
        # get known objects from skin layer and from portal:
        for name in names:
            self.assertTrue(getattr(skin, name, None) is not None)
            self.assertTrue(getattr(self.portal, name, None) is not None)


# Import
import cv2
import numpy as np
from matplotlib import pyplot as plt
import imutils
from skimage.segmentation import clear_border
import skimage.morphology as mp
import scipy.ndimage as sm
import os

# 알약 사진(좌우)를 2장으로 만들고 불필요한 여백을 제거합니다.
# https://youbidan.tistory.com/19
# C:\_projects\삼육대인공지능\medication-grain-identification-data\docs\raw_image\1NNuh6mJnYK.jpg
# path_dir = "docs/raw_image/"
path_dir = "docs/raw_image/"
save_dir = "docs/classification2/"
file_list = os.listdir(path_dir)

def createFolder(directory):
    try:
        if not os.path.exists(directory):
            os.makedirs(directory)
    except OSError:
        print ('Error: Creating directory. ' +  directory)

createFolder(save_dir)
index = 0
for file_name in file_list:
	index = index + 1
	if index == 20056 or index == 20057:
		path = path_dir + file_name
		image_origin = cv2.imread(path)
		file_basename, file_ext = os.path.splitext(file_name)
		print(file_name)
		createFolder(save_dir+'etc/')
		cv2.imwrite(save_dir+'etc/' +file_basename+file_ext, image_origin)
		continue
	# if index in range(0, 30000):
	# 	continue
	path = path_dir + file_name
	file_basename, file_ext = os.path.splitext(file_name)
	image_origin = cv2.imread(path)

	# 하단 footer 기준 이미지 분리
	h, w = image_origin.shape[:2]
	b,g,r = image_origin[h-10][10]

	# image_origin[h-10][10] = (255, 0, 0)
	footer_rgb = str(round(r/20))+str(round(g/20))+str(round(b/20))
	size = str(w)+'x'+str(h)
	size_footer_folder_path = size + "_" + footer_rgb

	save_path = save_dir + size_footer_folder_path
	createFolder(save_path)
	cv2.imwrite(save_path +'/'+ file_basename+file_ext, image_origin)
	print(index)


import xml.etree.cElementTree as ET
import pprint
import re
from collections import defaultdict

filename = 'C:/Users/butte/Documents/Udacity/P3/Project/map_sample'

def clean(arg):
    return arg[1:].strip('\'')

def unique_k(filename, k):
    k_set = set()
    for event, elem in ET.iterparse(filename, events=("start",)):
        if elem.tag == "node" or elem.tag == "way":
            for tag in elem.iter("tag"):
                if tag.attrib['k'] == k:
                    k_set.add(clean(str((tag.attrib['v'].encode()))))
    return k_set

def clean_street_names(tag, expected_names, mapping):
    """function that takes a dictionary of tags and cleans
    street names according to the mapping supplied. Returns
    corrected tag_dict.
    tag - dict of tags - must be labelled with key and value
    expected_names - list of names that are accepted
    mapping - dict of erroneous names and corrections"""
    if tag['key'] == 'street':
        street_type = (str(tag['value']).split(' '))[-1]
        if street_type not in expected_names:
            print(tag['value'])
            try:
                new_name = tag['value'].replace(street_type,mapping[street_type])
                tag['value'] = new_name
                print(tag['value'])
            except(KeyError):
                print('Add ' + street_type + ' to mapping!')
    return tag

def clean_sports(tag, expected_names, mapping):
    """function that takes a dictionary of tags and cleans
    sports according to the mapping supplied. Returns
    corrected tag dictionery.
    tag - dict of tags - must be labelled with key and value
    expected_names - list of names that are accepted
        mapping - dict of erroneous names and corrections"""
    if tag['key'] == 'sport':
        sport_type = str(tag['value'])
        if sport_type not in expected_names:
            print(tag['value'])
            try:
                new_name = tag['value'].replace(sport_type,mapping[sport_type])
                tag['value'] = new_name
                print(tag['value'])
            except(KeyError):
                print('Add ' + sport_type + 'to mapping!')
    return tag

def clean_oneway(tag):
    """function that takes a dictionary of tags and cleans
    oneway definitions to be yes or no only. Returns 'unclassified'
     as value if yes or no not supplied
    tag - dict of tags - must be labelled with key and value"""
    expected_names = ['yes','no']
    if tag['key'] == 'oneway':
        oneway_type = str(tag['value'])
        if oneway_type not in expected_names:
            tag['value'] = 'unclassified'
    return tag

def add_postcode_to_suburb(tag_list,postcode_list):
    """Takes a dictionary of tag dictionaries for a given node and adds
    a postcode for a given suburb, according to the postcode list, if the
    tag has a suburb listed
    tag - dictionery of dictioneries of tags. Must be labeled key and value
    postcode_list - dictionary of suburbs as keys and postcodes as values"""
    change = False
    for tag in tag_list:
        if (tag['key'] == 'place') and (tag['value'] == 'suburb'):
            change = True
            tag_id = tag['id']
            tag_type = tag['type']
        if tag['key']  == 'name':
            suburb = tag['value']
    if change:
        try:
            postcode = postcode_list[suburb]
            tag_dict = {'id':tag_id,'value':postcode,'key':'postcode','type':tag_type}
            tag_list.append(tag_dict)
        except(KeyError):
            print('Add ' + suburb +' to postcode list!')
    return tag_list

# postcodes = unique_k(filename, 'addr:postcode')
# print(postcodes)
# street = unique_k(filename, 'addr:street')
# street_set = set()
# for i in street:
#     lst = str(i).split(' ')
#     street_set.add(lst[-1])
# print(street_set)
print(unique_k(filename, 'place'))
print(sorted(list(unique_k(filename, 'sport'))))

# website = unique_k(filename, 'oneway')
# lst = sorted(list(website))
# print(lst)


from pickletools import read_unicodestring8
from webdriver_manager.chrome import ChromeDriverManager
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

from collections import defaultdict
info = defaultdict(list)

import time
import datetime
import os

# Primary Setting
chrome_options = Options()
chrome_options.add_experimental_option('detach', True)
chrome_options.add_experimental_option('excludeSwitches', ['enable-logging'])
service = Service(executable_path=ChromeDriverManager().install())

# data crawling
save_rootpath = 'C:/Users/Gliver/Desktop/tmp'
food_list = ['김치', '비빔밥', '콩나물무침', '치킨', '돈까스', '계란', '오렌지주스', '쌀밥', '우유', '샐러드', '고구마맛탕']
image_num = 10

for food in food_list:
    driver = webdriver.Chrome(service=service, options=chrome_options)
    driver.implicitly_wait(5)
    driver.maximize_window()

    try:
        os.mkdir(f'{save_rootpath}/{food}')
    except:
        pass
    url = f'https://search.naver.com/search.naver?where=image&sm=tab_jum&query={food}'

    driver.get(url)
    time.sleep(1)

    for i in range(image_num // 10):
        driver.execute_script('scrollBy(0, 500)')
        time.sleep(2)
    time.sleep(5)

    elements = driver.find_elements(By.CSS_SELECTOR, '._image')

    num = min(image_num, len(elements))
    for idx, element in enumerate(elements[:num], 1):
        image_src = element.get_attribute('src')
        url = image_src[:image_src.find('&type')]

        save_path = f'{save_rootpath}/{food}/img_{idx}.jpg'
        os.system(f"curl  {url}  > {save_path}")

        print(image_src)
        print(f"curl  {url}  > {save_path}")
        print()

    print(len(elements))
    driver.close()
    print('Good')


import os
import pandas as pd
import random

from sklearn import preprocessing

from sklearn.neural_network import MLPClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
from sklearn.gaussian_process import GaussianProcessClassifier
from sklearn.gaussian_process.kernels import RBF
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier, AdaBoostClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.discriminant_analysis import QuadraticDiscriminantAnalysis

from sklearn import metrics
from sklearn.metrics import confusion_matrix
from sklearn.metrics import log_loss

from sklearn import svm

# 150 web sites for training 50 web sites for testing - features and classification

def classification():
    train, test = flat_train_test_df()

    X_train, y_train = train.drop('relevantImg', axis=1), train['relevantImg']
    X_test, y_test = test.drop('relevantImg', axis=1), test['relevantImg']
    names = ["AdaBoost", "Random Forest", "Decision Tree", "RBF SVM", "Nearest Neighbors", "Neural Net",
         "Naive Bayes", "ADA-RF"]
    classifiers = [
    KNeighborsClassifier(weights='distance'),
    AdaBoostClassifier(),
    RandomForestClassifier(),
    DecisionTreeClassifier(),
    SVC(),
    MLPClassifier(),
    GaussianNB(),
    AdaBoostClassifier(RandomForestClassifier()),
    ]

    for name, clf in zip(names, classifiers):
        clf = clf.fit(X_train, y_train)
        y_pred = clf.predict(X_test)
        # prediction
        calculate_res(y_test, y_pred, name, "All web sites")
    return

def calculate_res(y, pred, test_name, theWebsite):
    tn, fp, fn, tp = confusion_matrix(y,  pred).ravel()
    logL = log_loss(y,  pred)
    acc, recall, pre, fmeasure = (tn+tp)/(tn + fp + fn + tp), (tp)/(fn + tp), (tp)/(fp + tp), (2*tp) / (2*tp + fp + fn)
    print('{} {}: Accuracy={} Recall={} Precision={} F-Measure={} logLoss={} '.format(test_name ,theWebsite, acc, recall, pre, fmeasure, logL))

def flat_train_test_df():
    df = pd.read_csv('train_image_csv.csv', sep=',')
    le = preprocessing.LabelEncoder()
    le.fit(df['fileext'].astype(str))
    df['fileext'] = le.transform(df['fileext'])
    le.fit(df['parent1tag'].astype(str))
    df['parent1tag'] = le.transform(df['parent1tag'])
    le.fit(df['parent2tag'].astype(str))
    df['parent2tag'] = le.transform(df['parent2tag'])

    dft = pd.read_csv('test_image_csv.csv', sep=',')
    le = preprocessing.LabelEncoder()
    le.fit(dft['fileext'].astype(str))
    dft['fileext'] = le.transform(dft['fileext'])
    le.fit(dft['parent1tag'].astype(str))
    dft['parent1tag'] = le.transform(dft['parent1tag'])
    le.fit(dft['parent2tag'].astype(str))
    dft['parent2tag'] = le.transform(dft['parent2tag'])

    return df, dft

classification()


###############################
# Trainning with Support Vector Machine
# Jose Barreiros, Alec Cornwell
# Jun, 2018
################################

import sys, cv2, argparse, time, pdb, pickle, random
import numpy as np
from trainning_v4 import Feature
from read_Dtr import Read_Dtr
from MergedData import merge_data
from sklearn import svm
from sklearn.grid_search import GridSearchCV

def get_labeled_data(Dtr):
	#pdb.set_trace()
	X=[]
	Y=[]
	for i in range(0,len(Dtr)):
		X.append(Dtr[i].mean_intensity[:,3])
		Y.append(Dtr[i].segmentID)

	X=np.asarray(X)
	Y=np.asarray(Y)

	return (X,Y)

def yes_or_no(question):
	while "the answer is invalid":
		reply = str(input(question+' (y/n): ')).lower().strip()
		if reply[0] == 'y':
			return True
		if reply[0] == 'n':
			return False

def randomize_order(X,Y):
	assert len(X) == len(Y)
	p = np.random.permutation(len(X))
	return(X[p],Y[p])

def split_data(X,Y,percentage_tr):
	#pdb.set_trace()
	size=len(X)
	size_tr=int(size*percentage_tr)

	ran_index = np.random.permutation(size)
	tr_index=ran_index[:size_tr]
	te_index=ran_index[size_tr:]

	Xtr=X[tr_index]
	Ytr=Y[tr_index]

	Xte=X[te_index]
	Yte=Y[te_index]

	return (Xtr,Ytr,Xte,Yte)

def main():
	#For single file
	name_f = input("What file do you want to open: ")
	#name_f="jun14_330pm_dtr.p"
	Dtr=Read_Dtr(name_f)

	#For merging files
	#name_f_D1="jun14_330pm_dtr.p"
	#name_f_D2="ss_dtr.p"
	#Dtr=merge_data(name_f_D1,name_f_D2)

	(X,Y)=get_labeled_data(Dtr)

	(X,Y)=randomize_order(X,Y)
	(Xtr,Ytr,Xte,Yte)=split_data(X,Y,0.7)

	clf=svm.SVC()
	#param_grid = {
 	#	"kernel" : ['linear', 'rbf'],
	#	"gamma" : [1e-1, 1e-2],
	#	"C" : [1, 10, 100]}

	#clf=GridSearchCV(svm.SVC(), param_grid, cv=10, scoring='accuracy')
	clf.fit(Xtr,Ytr)
	print("SVM Model succesfully acquired")
	print(clf)
	#clf.best_params_
	#print(clf.best_params_)
	reply1=yes_or_no("Do you want to save this ML model?")
	if reply1==True:
		name_f = input("\nName your ML Model file: ")
		f=open(name_f+".model","wb")
		pickle.dump(clf,f)
		pickle.dump(clf,f)
		f.close()

	Ytr_predicted=clf.predict(Xtr)
	#pdb.set_trace()
	acc=(Ytr.astype('str')==Ytr_predicted).sum()/len(Ytr)
	print("\nTrainning Acc: %f" %acc)

	Yte_predicted=clf.predict(Xte)
	acc=(Yte.astype('str')==Yte_predicted).sum()/len(Yte)
	print("\nTesting Acc: %f" %acc)

	#pdb.set_trace()
	reply=yes_or_no("Do you validate the model?")
	if reply==True:
		name_f = input("What is your validation file?: ")
		#name_f="jun15_1pm_val_dtr .p"
		Dva=Read_Dtr(name_f)

		#name_f_D1="val_jun14_6pm_dtr.p"
		#name_f_D2="ss_val_dtr.p"
		#Dva=merge_data(name_f_D1,name_f_D2)

		(Xva,Yva)=get_labeled_data(Dva)
		(Xva,Yva)=randomize_order(Xva,Yva)
		Yva_predicted=clf.predict(Xva)
		acc=(Yva.astype('str')==Yva_predicted).sum()/len(Yva)
		print("\nValidation Acc: %f" %acc)
		#pdb.set_trace()

if __name__ == '__main__':
    #print("This only executes when %s is executed rather than imported" % __file__)
	main()


import bpy

bl_info = {
    "name": "Assign Per-Face Materials",
    "description": "Assigns a material to every face in the object.",
    "author": "Jacob Falck",
    "blender": (3, 1, 0),
    "version": (1, 0, 0),
    "location": "",
    "warning": "",
    "wiki_url": "",
    "tracker_url": "",
    "category": "Material"
}

class AUTOSLOTS_OT_auto_assign_slots(bpy.types.Operator):
    """Tooltip."""
    bl_idname = "facemat.assign_mat_per_face"
    bl_label = "Assign Materials to All Faces"
    bl_space_type = 'VIEW_3D'

    def execute(self, context):

        ob = context.object
        mesh = ob.data

        for face in mesh.polygons:

            # unfortunatley, this creates duplicates of materials that are already present.
            # but it doesn't matter much. unassigned materials can be removed at a later point.
            if len(ob.material_slots) > 0:
                if ob.material_slots[face.material_index] is not None:
                    mat = ob.material_slots[face.material_index].material
            else:
                mat = bpy.data.materials.new(name="Empty Material")

            ob.data.materials.append(mat)
            face.material_index = len(ob.data.materials) - 1

        return {"FINISHED"}

def draw_menu(self, context):
    self.layout.operator("facemat.assign_mat_per_face")

def register():
    bpy.utils.register_class(AUTOSLOTS_OT_auto_assign_slots)
    bpy.types.VIEW3D_MT_object_context_menu.append(draw_menu)

def unregister():
    bpy.types.VIEW3D_MT_object_context_menu.append(draw_menu)
    bpy.utils.unregister_class(AUTOSLOTS_OT_auto_assign_slots)

if __name__ == "__main__":
    register()


import matplotlib.pyplot as pyplt
import numpy as np
import math
import sys
from scipy.signal import argrelmax, argrelmin

#this library contains different CFD methods that may be used for on-the-fly processing

###########################################
#Ave CFD section
###########################################
def aveGattonCFD_main(dataIn_Amplitude):
	CFD_TH = 10.  # the noise is scaled to approximately 5. This sets the threshold to 10x the noise level.
	CFD_offset = 5
	conv_len = 3

	wf_amp = dataIn_Amplitude

	#these look like threshold calculations
	wf_amp_avg = np.sum(wf_amp) / wf_amp.shape[0]
	wf_amp_sigma = .5 * np.sqrt(np.sum((wf_amp - wf_amp_avg) ** 2) / wf_amp.shape[0])
	wf_amp = (wf_amp - wf_amp_avg) / wf_amp_sigma
	#this loop is executed once.  not apparent why it is in a loop statement
	for _ in np.arange(1):
		#interesting choice of convolution filter
	    wf_amp = np.convolve(wf_amp, np.concatenate((np.ones(conv_len), np.zeros(conv_len))) / conv_len,
	                         mode='same')

	#some sort of thresholding
	wf_histo, wf_bins = np.histogram(wf_amp, bins=100)
	wf_log_histo = np.log(wf_histo)
	good_value_cut = np.logical_not(np.isinf(wf_log_histo))
	coeff = np.polynomial.polynomial.polyfit(x=wf_bins[:-1][good_value_cut], y=wf_log_histo[good_value_cut], deg=5)
	sigma = np.sqrt(-1. / coeff[2])
	CFD_TH = 3.0 * sigma

	boolean_threshold = np.logical_or(wf_amp > CFD_TH, wf_amp < -CFD_TH)
	boolean_zero_padding = np.logical_and(np.append(np.logical_not(boolean_threshold), np.zeros(CFD_offset)),
	                                      np.append(np.zeros(CFD_offset), boolean_threshold))[0:-CFD_offset]

	wf_zeroed = wf_amp.copy()
	wf_zeroed[boolean_zero_padding] = 0

	#i think boolean_select is True at indices that contain a found hit?
	boolean_select = np.logical_or(boolean_threshold, boolean_zero_padding)

	th_wave = wf_zeroed[boolean_select]

	indexList = np.arange(1, (len(wf_amp) + 1))
	th_time_wave = indexList[boolean_select]

	#some sort of traditional CFD method
	CFD_input = np.append(th_wave, np.zeros(CFD_offset))
	CFD_shift_scale = np.append(np.zeros(CFD_offset), th_wave)
	CFD_wave = CFD_input - CFD_shift_scale

	CFD_wave_pos = np.where(CFD_wave > 0, True, False)
	CFD_wave_neg = np.where(CFD_wave <= 0, True, False)
    #zero_points is a zero crossing within a local subset?
	zero_points = np.logical_and(CFD_wave_pos[:-1], CFD_wave_neg[1:])
	time_locs = th_time_wave[:-1][zero_points[5:]]

	return dataIn_Amplitude, time_locs

##################################################
#Andrei CFD section
##################################################
def andreiKamalovCFD_main(dataIn):
	#initialize 'hitIndices', which will contain the indices of any hits found in the trace supplied as 'dataIn_Amplitude'
	hitIndices = []

	#subtract a mean offset
	dataIn_Centered = dataIn - np.mean(dataIn)
	#calculate the variance of the trace
	sigma = np.std(dataIn_Centered)

	#calculate an upper threshold above which to look for peaks in the raw trace
	threshold = 4*sigma
	#return the indices for which the raw data exceeds the threshold.
	dataIn_AboveThreshold_Indices = np.flatnonzero(dataIn_Centered > threshold)

	#if it's likely that there are zero hits in this trace, there's no need to perform the remainder of the CFD processing.
	if(len(dataIn_AboveThreshold_Indices) == 0):
		#create an empty array of found hits
		#NOT IMPLEMENTED YET BUT SHOULD BE
		return dataIn_Centered, hitIndices

	#convolve the raw data with a triangular filter
	convFilterLength = 35#this must be an odd value
	convolvedData = convoluteByTriangle(dataIn_Centered, convFilterLength)

	#add up an inverse and an offset.  this is the type of approach an electronic CFD performs.
	lengthTrace = len(convolvedData)
	CFDOffset = 20
	inverseMultiplier = -0.75
	offsetTrace = convolvedData[0:(lengthTrace - CFDOffset)]
	inverseTrace = inverseMultiplier * convolvedData[CFDOffset:lengthTrace]
	#traditional CFD adds a time-offset copy of the trace with an inverser copy of original trace.
	comparedTrace = offsetTrace + inverseTrace
	#shift the region with zero-point crossing to be more centered on the zero cross.  The initial array is found based on being above some amount of standard deviations
	indicesShift = round(CFDOffset * (1 + inverseMultiplier))
	dataIn_AboveThreshold_Indices -= indicesShift

	#call a method which will take the array of indices, and separate that one array into a set of arrays, wherein each array is a continuous set of integers.
	tupleOfRegionIndicesArrays = separateArrayIntoTupleOfContinuousArrays(dataIn_AboveThreshold_Indices)

	#findZeroCrossings for each array of continuous integers
	for ind in range(len(tupleOfRegionIndicesArrays)):
		seriesToProcess = tupleOfRegionIndicesArrays[ind]
		#method 'findZeroCrossings' inspects a series to validate it.  if it's a good zero-crossing, it returns: True, indexOfCrossing.  if it's a bad series, the return is 'False, 0'
		validSeriesFlag, hitIndex = findZeroCrossings(seriesToProcess, comparedTrace)
		#append good hits to the array 'hitIndices'
		if(validSeriesFlag):
			hitIndices.append(hitIndex)

	#there are now a set of found hitIndices.  but these are in respect to the processed comparedTrace.  need to un-shift the indices to represent hits for the actual trace (dataIn_Centered)
	hitIndices = [x + indicesShift for x in hitIndices]

	#control whether to do diagnostic plots or not
	if(False):
		halfSpan = 200
		for ind in range(len(hitIndices)):
			#diagnostic plots
			lowBound = hitIndices[ind].item() - halfSpan
			highBound = hitIndices[ind].item() + halfSpan
			pyplt.plot(range(lowBound, highBound), convolvedData[lowBound:highBound])
			if (len(hitIndices) > 0):
				pyplt.scatter(hitIndices[ind].item(), convolvedData[hitIndices[ind].item()])
			pyplt.show()

			pyplt.plot(range(lowBound, highBound), dataIn_Centered[lowBound:highBound])
			if (len(hitIndices) > 0):
				pyplt.scatter(hitIndices[ind].item(), dataIn_Centered[hitIndices[ind].item()])
			pyplt.show()

	return dataIn_Centered, hitIndices

#CFD that finds hits based on statistical analysis of the raw trace.  The main idea is to look at the stastitical probability of each trace having it's magnitude.  Look for clusters that are above some threshold, and consider those clusters as unique.  Then look through the cluster to see if theres multiple separate hits within it.
zScoreUnversal = 6
def andreiKamalovCFD_statistical(dataIn, noiseRegionLimitLow=0, noiseRegionLimitHigh=1000):
	hitIndices = []
	hitLimitsHigh = []
	convPeakMax = []

	#normalize the trace to be positive, and have max value of +1
	normedTrace = normalizeTrace(dataIn)
	# dataInNormalize = normedTrace
	dataInNormalize = np.diff(normedTrace)
	#use the suggested noise region to establish some understanding of the trace and it's signal/noise ratio
	stdDev = np.std(dataInNormalize[noiseRegionLimitLow:noiseRegionLimitHigh])
	#convert trace to a series of z-scores
	zScoresArray = dataInNormalize/stdDev

	#convolve zScoresArray across some length
	minimumWidthOfHit = 9
	convolved_zScores = np.convolve(zScoresArray, np.ones(minimumWidthOfHit), 'same')

	#use convolved z-scores array to look for local maxima
	findMaxIndices = argrelmax(convolved_zScores)
	findMaxIndices = findMaxIndices[0]#unwrap the output of argrelmax

	firstRealPeakZScore = 0
	firstPeakEstablished = False
	#look through each local maxima
	for localMax in findMaxIndices:
		combined_zScoreHere = convolved_zScores[localMax]
		#check whether the current local maxima meets criteria for certainty that a peak is found
		if combined_zScoreHere/math.sqrt(minimumWidthOfHit) > zScoreUnversal:
			#check if localMax is already accounted for in between a previously located low and high limit pair
			if not checkIfAlreadyConsidered(localMax, hitIndices, hitLimitsHigh):

				if not firstPeakEstablished:
					firstRealPeakZScore = combined_zScoreHere
					firstPeakEstablished = True

				if combined_zScoreHere > 0.2*firstRealPeakZScore:
					#current maxima believed to be a legitimate peak.  Process it to isolate the domain of the peak.
					peakLimitLow, peakLimitHigh = isolatePeakBy_zScores(zScoresArray, localMax, minimumWidthOfHit)

					#check if this is possible ringing - basic check is to see if the start is negative valued and if the positive value is not that much larger in terms of magnitude
					if (normedTrace[peakLimitLow] < 0) and (np.absolute(normedTrace[peakLimitHigh]) <= 3.5*np.absolute(normedTrace[peakLimitLow])):
						#this could be a ringing peak, better to drop it
						pass
					else:
						hitIndices.append(peakLimitLow)
						hitLimitsHigh.append(peakLimitHigh)
						convPeakMax.append(localMax)

	# #go backwards from end to start, and eliminate peaks that begin too soon after the end of the previous peak.  This helps eliminate ripples that the algorithm claims are separate peaks, but are actually part of the previous peak, just separated by a short burst of low z-score
	# for i in range(len(hitIndices)-1, 0, -1):
	# 	if ((hitIndices[i] - hitLimitsHigh[i-1]) < 7):
	# 		#hit is too close to previous hit.  remove it from the list
	# 		hitIndices.pop(i)
	# 		hitLimitsHigh.pop(i)
	# 		convPeakMax.pop(i)

	#convert hitIndices list into an array
	hitIndices = np.asarray(hitIndices)
	hitLimitsHigh = np.asarray(hitLimitsHigh)
	convPeakMax = np.asarray(convPeakMax)

	return dataIn, hitIndices, hitLimitsHigh, convPeakMax

#quick hack to apply CFD to MCP direct output.  This is a quick hack to use the normal CFD but negate the data to help it find the hits associated with the MCP readout.
def andreiKamalovCFD_MCPHack(dataIn):
	dataInNegated = -1 * dataIn

	dataOut_Centered, hitIndices = andreiKamalovCFD_main(dataInNegated)

	return dataOut_Centered, hitIndices

#####################################################################################
#support methods for andrei's CFD

#convolute the array 'signalIn' by a triangular waveform, with true width (binWidth + 2).  the two extra bits are for the 'zero' value of the triangles convolution.  the max height occurs at the central bin.  think of the convolution filter as a sawtooth.
def convoluteByTriangle(signalIn, binWidth):
	#convoluteByTriangle requires an odd value for 'binWidth' to work.

	#construct triangular convolution pattern
	multipliers = np.zeros(binWidth)
	numTriangleSide = ((binWidth+1)//2)#this is the number of non-zero points associated with the triangular pattern of the conv filter
	#run a for loop to populate the convolution filter
	for ind in range(0, numTriangleSide):
		#populate the leading side of triangular filter
		multipliers[ind] = (ind+1)/numTriangleSide
		#populate the falling side of the triangular filter
		multipliers[binWidth - 1 - ind] = (ind+1)/numTriangleSide
	normFactor = np.sum(multipliers)
	multipliers = multipliers/normFactor

	lengthData = len(signalIn)
	#apply the convolution filter
	#convolution produces a list of the same length + 2*offsets
	offsets = (binWidth - 1)//2
	convolvedData = np.zeros(lengthData + (binWidth - 1))#populate a list of zeroes
	for ind in range(0, binWidth):
		#apply convolution
		convolvedData[ind:(lengthData+ind)] += multipliers[ind] * signalIn

	#return the subset of the convolved data that represents the correct data length
	return convolvedData[offsets:(lengthData + offsets)]

#this method is designed to take an array of integers, some of which are continuous, and separate it into a set of arrays wherein each array is a continuous set of integers.  these individual arrays are placed into a tuple that is then returned.
def separateArrayIntoTupleOfContinuousArrays(dataIn_AboveThreshold_Indices):
	#setup the 'first' currentList and the tuple that will be populated
	currentList = []
	tupleOfLists = ()

	#handle the odd case that there is exactly 1 index found.  This is a rarity, but it needs to be handled to avoid error
	if len(dataIn_AboveThreshold_Indices) == 1:
		currentList += dataIn_AboveThreshold_Indices[0]
		tupleOfLists += (currentList,)
	#the cases which matter are the ones that have more than one element, and are handled in the else statement
	else:
		for ind in range(0, len(dataIn_AboveThreshold_Indices) - 1):
			#add the current index to the current list
			currentList.append(dataIn_AboveThreshold_Indices[ind])

			#inspect whether the next element in the list of indices is the start of a new continuous set.  if it is, close out this list
			if (dataIn_AboveThreshold_Indices[ind + 1] - dataIn_AboveThreshold_Indices[ind]) != 1:
				#the next index is a the start of a new continuous set
				tupleOfLists += (currentList,)
				#clear the currentList, so that the next value considered will be the first value in a new array
				currentList = []

		#process the final index in the array, and close out the current list since the list of indices is complete
		currentList.append(dataIn_AboveThreshold_Indices[-1])
		tupleOfLists += (currentList,)

	return tupleOfLists

#method findZeroCrossings inspects the index series in seriesToProcess, and verifies that the associated y-values in comparedTrace are an appropriate rising edge.  if it's a good series, return true and the zero crossing index.  if false, return False and 0
def findZeroCrossings(seriesToProcess, comparedTrace):
	numIndices = len(seriesToProcess)
	if numIndices <= 1:
		#series of length 1 won't have a proper zero crossing and are therefore, not valid zero crossings
		return False, 0
	else:
		#the ideal zero crossing series starts negative and trends positive.  it is good to filter series for validity by verifying this.
		seriesLowest = seriesToProcess[0]
		seriesHighest = seriesToProcess[-1]

		#verify that series crossing isn't too close to either start or end of the trace.  If it is too near to either, can't test whether zero crossing is valid.
		#Note that the condition checks look at seriesLowest - 1 and seriesHighest + 1.  This is because the way the while loops go through below, the loop can cause either indLow or indHigh to go out of bounds of comparedTrace, and then require a call to comparedTrace with an invalid index on the next boolean condition check.
		if (seriesLowest - 1) < 0 or seriesHighest < 0:
			#verify that the seriesToProcess does not include negative integers - that is, that it is not too close to the start of trace to pass the test
			#if it is, return that the series is not valid
			return False, 0
		elif (seriesHighest + 1) >= len(comparedTrace) or seriesLowest >= len(comparedTrace):
			#verify that the seriesToProcess is not too close to the end of the trace - that is, verify it isn't at the cutoff edge of the time axis.
			#if it is, return that the series is not valid
			return False, 0

		#inspect where the series stops being negative
		indLow = seriesLowest
		while (comparedTrace[indLow] < 0) and (indLow <= seriesHighest):
			indLow += 1

		#inspect where the series stops being positive if coming in from the positive side
		indHigh = seriesHighest
		while (comparedTrace[indHigh] > 0) and (indHigh >= seriesLowest):
			indHigh -= 1

		#if indLow and indHigh are adjacent to each other, then the series passed in was a monotonically positive zero-crossing.
		if ((indHigh + 1) == indLow): #the way the while loops are broken out of, it's a valid series if indLow is one value higher than indHigh
			#return true, and the index of the first positive value after the crossing.
			return True, indHigh
		else:
			#this was not a valid series
			return False, 0

#function to normalize a trace to be positive, such that the max of the trace is 1.
def normalizeTrace(dataIn):
	#ensure that the dataIn value is normalized to zero.  Do this by finding a median value of the trace
	dataInNorm = dataIn - np.median(dataIn)
	#normalize the data such that the highest point has absolute value of 1.  First, find the maximal value but also figure out if peak is upwards or downwards going
	maximalValueAbs = np.absolute(np.max(dataInNorm))
	minimalValueAbs = np.absolute(np.min(dataInNorm))
	if(maximalValueAbs > minimalValueAbs):
		#the peak is positive going.  normalize with positive value
		dataInNorm = dataInNorm/maximalValueAbs
	else:
		#the peak is negative going.  normalize with negative value
		dataInNorm = -1*dataInNorm/minimalValueAbs

	return dataInNorm

#a suspected peak was found based on combined z-scores.  find a good length for it based on the middle.
def isolatePeakBy_zScores(zScoresArray, localMaxIndex, minimumWidthOfHit):
	#I should use indices (localMaxIndex-minimumWidthOfHit/2):(localMaxIndex+minimumWidthOfHit/2).  IF supplied width is odd, give preference to rising edge

	streakBreakerCount = 3
	thresholdScore = zScoreUnversal
	#denote the starintg upper and lower boundaries for the peak.  calling method provides some minimal width.
	indexCutoffLow = localMaxIndex - int(np.ceil(minimumWidthOfHit/2))#set starting high cutoff as max index minus half the width
	indexCutoffHigh = localMaxIndex + int(np.floor(minimumWidthOfHit/2)) - 1#set starting high cutoff as max index plus half the width
	if indexCutoffHigh >= len(zScoresArray):
		indexCutoffHigh = len(zScoresArray) - 1
	#start by trying to find the later times for which the peak still seems statistically significant
	current_zScoreSum = np.sum(zScoresArray[indexCutoffLow:(indexCutoffHigh + 1)])
	currentLength = indexCutoffHigh - indexCutoffLow + 1
	lastImproved_zScore = (current_zScoreSum/math.sqrt(currentLength))
	lastImproved_zScoreSum = current_zScoreSum
	currentStrikes = 0
	#loop through later times, increasing the upper x-limit of the found peak until enough strikes have been taken
	while currentStrikes < streakBreakerCount:
		#find new variables associated with an incremented width of peak

		if(indexCutoffHigh + 1 == len(zScoresArray)):
			break
			breakpoint()
		else:
			indexCutoffHigh += 1

		currentLength += 1
		current_zScoreSum = current_zScoreSum + zScoresArray[indexCutoffHigh]
		new_zScore = (current_zScoreSum/math.sqrt(currentLength))
		#see if adding this index helped the cumulative z-score or not
		if new_zScore >= lastImproved_zScore:
			#expanding to this index benefits the cumulative z-score, so thi series is likely part of the hit
			lastImproved_zScore = new_zScore
			lastImproved_zScoreSum = current_zScoreSum
			#upper index has been increased, reset the strike count so method can continue to try expanding the peak length
			currentStrikes = 0
		else:
			#expanding to this index hurts the cumulative z-score metric.  Would be better to not include this index as part of the found hit
			currentStrikes += 1

	#the while loop has been exited, suggesting that the max permissible number of strikes has been reached while trying to expand the peak.  This number of indices appended to the peak on the positive side failed to improve the cumulative z-score, and they should be removed from the final result
	indexCutoffHigh -= streakBreakerCount
	currentLength -= streakBreakerCount

	#perform a similar process to expand the lower time limit of the found peak
	currentStrikes = 0
	current_zScoreSum = lastImproved_zScoreSum
	#loop through earlier times, decreasing the lower x-limit of the found peak until strike limit has been reached
	while currentStrikes < streakBreakerCount:
		indexCutoffLow -= 1
		currentLength += 1
		current_zScoreSum = current_zScoreSum + zScoresArray[indexCutoffLow]
		new_zScore = (current_zScoreSum/math.sqrt(currentLength))
		#see if adding this index helped the cumulative z-score or not
		if new_zScore >= lastImproved_zScore:
			#expanding to this index benefits the cumulative z-score, so thi series is likely part of the hit
			lastImproved_zScore = new_zScore
			lastImproved_zScoreSum = current_zScoreSum
			#upper index has been increased, reset the strike count so method can continue to try expanding the peak length
			currentStrikes = 0
		else:
			#expanding to this index hurts the cumulative z-score metric.  Would be better to not include this index as part of the found hit
			currentStrikes += 1

	#the while loop has completed, meaning the max permissible of invalid indices on the lower limit have been investigated across.  Undo their effects here
	indexCutoffLow += streakBreakerCount
	currentLength -= streakBreakerCount

	#the cutoff indices found thus far normally under-represent the width of the peak.  This is because of how small the noise floor can be compared to a good peak.  Can optionally continue to add tid-bit sections until the noise is confidently encountered.
	#this double while loop below will scout out regions of incrementally smaller steps to check them for whether they may be added on or not.
	lengthsToCheck = 3
	keepAddingHighSide = True
	addOnScoreThresh = 2
	while lengthsToCheck > 0:
		while keepAddingHighSide:
			if(indexCutoffHigh + lengthsToCheck >= len(zScoresArray)):
				break
			zScoreSumAddOn = np.sum(zScoresArray[indexCutoffHigh:(indexCutoffHigh + lengthsToCheck)])
			if (zScoreSumAddOn/math.sqrt(lengthsToCheck)) > addOnScoreThresh:
				indexCutoffHigh += lengthsToCheck
				currentLength += lengthsToCheck
			else:
				keepAddingHighSide = False

		#reset the loop for the next, shorter checking iteration.
		lengthsToCheck -= 1
		keepAddingHighSide = True

	#repeat the process for the lower cutoff index.
	lengthsToCheck = 3
	keepAddingLowSide = True
	addOnScoreThresh = 2
	while lengthsToCheck > 0:
		while keepAddingLowSide:
			zScoreSumAddOn = np.sum(zScoresArray[(indexCutoffLow-lengthsToCheck):indexCutoffLow])
			if (zScoreSumAddOn/math.sqrt(lengthsToCheck)) > addOnScoreThresh:
				indexCutoffLow -= lengthsToCheck
				currentLength += lengthsToCheck
			else:
				keepAddingLowSide = False

		#reset the loop for the next, shorter checking iteration.
		lengthsToCheck -= 1
		keepAddingLowSide = True

	#return x-limits of the found hit
	return indexCutoffLow, indexCutoffHigh

#method for checking whether a peak is in fact a peak and not probable ringing
def validatePeak(dataIn, peakIndex):
	spanToIntegrateAcrossPerDirection = 30

	#figure out limits across which to integrate to do the peak validation
	indexIntegralLow = peakIndex - spanToIntegrateAcrossPerDirection
	if indexIntegralLow < 0:
		indexIntegralLow = 0

	indexIntegralHigh = peakIndex + spanToIntegrateAcrossPerDirection
	if indexIntegralHigh >= len(dataIn):
		indexIntegralHigh = len(dataIn) - 1

	integral = np.sum(dataIn[indexIntegralLow:indexIntegralHigh])

	if integral >= 0:
		return True
	else:
		return False

# #test whether there is a double peak that is about to be reported.
# def postFindValidate(dataIn, peakLimitLow, peakLimitHigh):
# 	convolvedTrace = np.convolve(dataIn[peakLimitLow:peakLimitHigh], np.ones(7), 'same')

# 	#test whether theres more than one peak, and handle the situation if so.
# 	foundPeaks = argrelmax(convolvedTrace)
# 	if len(foundPeaks[0]) == 1:
# 		return True
# 	else:
# 		return False

#This method will take a supplied range and inspect it to be either a single peak or a cluster of multiple peaks that never return to noise floor level.  IF it is a multipeak, it goes ahead and separates the conglomerate by separating based on local minima.
def separateStructureIntoUniquePeaks(normalizedData, startOfStructure, endOfStructure):
	#convolve the subset of the trace that is believed to be a multi-peak
	convolvedSubTrace = np.convolve(normalizedData[startOfStructure:endOfStructure], np.ones(31), 'same')

	#look for breaking points.  minima probably serve as good as anything
	peakMaxima = argrelmax(convolvedSubTrace) + startOfStructure
	peakMaxima = peakMaxima[0]
	foundMinima = argrelmin(convolvedSubTrace) + startOfStructure
	foundMinima = foundMinima[0]
	#the code expects there to be one more maxima than minima.  if this is not the case, there are a number of potential causes, and it seems non-trivial to figure out what to do.
	if peakMaxima.size == foundMinima.size + 1:
		# may proceed
		pass
	else:
		#something weird happening with max/min finders.  cannot split a peak up into sub-components.
		peakStarts = np.zeros(1, dtype=int)
		peakStarts[0] = startOfStructure
		peakEnds = np.zeros(1, dtype=int)
		peakEnds[0] = endOfStructure
		return peakStarts, peakEnds, peakStarts

	# if peakMaxima.size > foundMinima.size:
	# 	#this is fine, we should have 1 more maxima than minima
	# 	pass
	# else:
	# 	if peakMaxima.size == foundMinima.size :
	# 		#likely have to delete a minima peak.  but first, make sure theyre not both zero
	# 		if peakMaxima.size == 0:
	# 			#they are both zero due to this and the previous if statements.  something has gone wrong with the man/min finders, return original series as single peak
	# 			#convert values from integer to array to be consistent with other output option.
	# 			peakStarts = np.zeros(1, dtype=int)
	# 			peakStarts[0] = startOfStructure
	# 			peakEnds = np.zeros(1, dtype=int)
	# 			peakEnds[0] = endOfStructure
	# 			return peakStarts, peakEnds, peakStarts
	# 		if foundMinima.size == 0 or peakMaxima.size == 0:
	# 			breakpoint()
	# 		#look for a faulty minima (minima that is between either startOfStructure and 1st maxima or last maxima and endOfStructure) and drop it
	# 		if foundMinima[0] < peakMaxima[0]:
	# 			#faulty minima is between start and 1st peak
	# 			foundMinima = foundMinima[1:]
	# 		elif foundMinima[-1] > peakMaxima[-1]:
	# 			#faulty minima lies between final maxima and end
	# 			foundMinima = foundMinima[0:-1]
	# 		else:
	# 			#not sure what went wrong, might be best to simply return initial conditions.
	# 			peakStarts = np.zeros(1, dtype=int)
	# 			peakStarts[0] = startOfStructure
	# 			peakEnds = np.zeros(1, dtype=int)
	# 			peakEnds[0] = endOfStructure
	# 			return peakStarts, peakEnds, peakStarts
	# 			# #error out and inform terminal
	# 			# sys.exit("ERROR: There is a mismatch between number of found local minima and maxima.  This means the code mut be further developed to figure out the origin of this.")

	if(len(peakMaxima) == 1):
		#there is only one peak here.  no need to do the other possible processing
		#convert values from integer to array to be consistent with other output option.
		peakStarts = np.zeros(1, dtype=int)
		peakStarts[0] = startOfStructure
		peakEnds = np.zeros(1, dtype=int)
		peakEnds[0] = endOfStructure
		return peakStarts, peakEnds, peakMaxima
	else:
		#there are truly multiple peaks here, and it is important to separate the construct into its sub-peaks

		#decide on number of sub-peaks to report back.  Use this number to initialize start/end lists
		numPeaks = foundMinima.size + 1
		peakStarts = np.zeros(numPeaks, dtype=int)
		peakEnds = np.zeros(numPeaks, dtype=int)
		#break up the start/end of structures with the discovered minima
		for i in range(numPeaks):
			if i == 0:
				#if first peak in series, the start is the overall structure start
				peakStarts[i] = startOfStructure
				peakEnds[i] = foundMinima[i] - 1#the subtract 1 is to not have overlap between what is considered end of a peak and start of a new one
			elif i == (numPeaks - 1):
				#if this is the final peak in the series, the end of the peak is the overall structure end
				peakStarts[i] = foundMinima[i - 1]
				peakEnds[i] = endOfStructure
			else:
				#use the found local minima as start/ends of the peaks in the multipeak structure
				peakStarts[i] = foundMinima[i - 1]
				peakEnds[i] = foundMinima[i] - 1#the subtract 1 is to not have overlap between what is considered end of a peak and start of a new one

		#return the indices at which the peaks are claimed to start and end at.
		return peakStarts, peakEnds, peakMaxima

#check if index localMax is already accounted for, by seeing if there's a pair of corresponding hitStarts and hitEnds that encompass localMax's index
def checkIfAlreadyConsidered(localMax, hitStarts, hitEnds):
	#set the default return value.  if a hit is found in previously accounted list, this flag is changed to true
	flagFound = False
	#figure out how many previous hits to check through
	numPreviousHits = len(hitStarts)

	#scan through each of the previous hits
	for i in range(numPreviousHits):
		#check if local max falls within the i'th previous hit
		if (localMax > hitStarts[i]) and (localMax < hitEnds[i]):
			#if it does, return True.
			flagFound = True
			return flagFound

	return flagFound


import hydra
from omegaconf import DictConfig, open_dict
import _main_jax
import _main_pytorch

@hydra.main(config_path="configs", config_name="config", version_base="1.1")
def my_app(config: DictConfig) -> None:
    with open_dict(config):
        config.wandb.group = config.mode
    if config.mode == "jax":
        _main_jax.my_app(config)
    elif config.mode == "pytorch":
        _main_pytorch.my_app(config)
    else:
        raise NotImplementedError

if __name__ == "__main__":
    my_app()


from __future__ import print_function, division
import os
import sys
sys.path.insert(0, os.path.dirname(__file__))

from tcr_errors import ChainMismatch, UnknownGene, SequenceMismatch
from tcr_utils import nt2aa, gene_to_num_str

class SequenceDefinition(object):
    """Class for TCR sequence type definition

    Determines what level of specificity of sequence definition will be used.
    Also includes magic methods for the operators =, <=, >=. This will indicate
    if a definition is

    Attributes/Keyword Arguments
    ----------------------------
    seq_type : 'ntseq' or 'aaseq'
        Defines whether the CDR3 sequence is defined on the nucleotide sequence
        or amino acid sequence level. (Default: 'ntseq')
    use_genes : bool
        If True the cassette gene information will be included. (Default: True)
    use_alleles : bool
        If True the allele information will be included in the sequence.
        (Default: False)
    use_chain : bool
        If True will specify whether it is a TRB or TRA chain. (Default: False)

    Methods
    -------
    get_seq_def()
        Returns dictionary version of sequence definition (useful for **kwargs).
    """

    def __init__(self, **kwargs):

        self.seq_type = kwargs.get('seq_type', 'ntseq')
        self.use_genes = kwargs.get('use_genes', True)
        self.use_alleles = kwargs.get('use_alleles', False)
        self.use_chain = kwargs.get('use_chain', False)

    def get_seq_def(self):
        """Returns the sequence definition dictionary

        Returns
        -------
        dict
            Sequence definition dictionary

        """
        return dict(seq_type = self.seq_type,
                    use_genes = self.use_genes,
                    use_alleles = self.use_alleles,
                    use_chain = self.use_chain)

    def __repr__(self):
        return "SequenceDefinition(%s)"%(', '.join([kw + '=' + str(kw_val) for kw, kw_val in self.get_seq_def().items()]))

    def __eq__(self, seq_def):
        if type(seq_def) == SequenceDefinition:
            seq_def = seq_def.get_seq_def()
        return self.get_seq_def() == seq_def

    def __le__(self, seq_def):
        if type(seq_def) == SequenceDefinition:
            seq_def = seq_def.get_seq_def()

        if self.seq_type == 'ntseq' and seq_def['seq_type'] == 'aaseq':
            return False

        return not any([self.__dict__[cond] for cond in ['use_genes', 'use_alleles', 'use_chain'] if not seq_def[cond]])

    def __ge__(self, seq_def):
        if type(seq_def) == SequenceDefinition:
            seq_def = seq_def.get_seq_def()

        if self.seq_type == 'aaseq' and seq_def['seq_type'] == 'ntseq':
            return False

        return all([self.__dict__[cond] for cond in ['use_genes', 'use_alleles', 'use_chain'] if seq_def[cond]])

class CloneDefinition(SequenceDefinition):
    """Class for T cell clone type definition

    Inherits from SequenceDefinition and adds the specification of what
    combination of TRB and TRA chains to use.

    Attributes/Keyword Arguments
    ----------------------------
    seq_type : 'ntseq' or 'aaseq'
        Defines whether the CDR3 sequence is defined on the nucleotide sequence
        or amino acid sequence level. (Default: 'ntseq')
    use_genes : bool
        If True the cassette gene information will be included. (Default: True)
    use_alleles : bool
        If True the allele information will be included in the sequence.
        (Default: False)
    use_chain : bool
        If True will specify whether it is a TRB or TRA chain. (Default: False)
    chains : list
        Specifies which chains (of TRB and TRA) are to be included.
        (Default: ['TRB', 'TRA'])

    Methods
    -------
    get_clone_def()
        Returns dictionary version of clone definition (useful for **kwargs).
    """

    def __init__(self, **kwargs):

        SequenceDefinition.__init__(self)

        self.chains = ['TRB', 'TRA']

        self.use_chain = True

        for kw, kw_val in kwargs.items():
            if kw in self.__dict__: self.__dict__[kw] = kw_val

    def get_clone_def(self):
        """Returns the clone definition dictionary

        Returns
        -------
        dict
            Clone definition dictionary

        """
        return dict(seq_type = self.seq_type,
                    use_genes = self.use_genes,
                    use_alleles = self.use_alleles,
                    use_chain = self.use_chain,
                    chains = self.chains)

    def __repr__(self):
        return "CloneDefinition(%s)"%(', '.join([kw + '=' + str(kw_val) for kw, kw_val in self.get_clone_def().items()]))

    def __eq__(self, clone_def):

        if type(clone_def) == CloneDefinition:
            clone_def = clone_def.get_clone_def()
        return self.get_clone_def() == clone_def

    def __le__(self, clone_def):
        if type(clone_def) == CloneDefinition:
            clone_def = clone_def.get_clone_def()

        if not set(self.chains).issubset(clone_def['chains']):
            return False

        if self.seq_type == 'ntseq' and clone_def['seq_type'] == 'aaseq':
            return False

        return not any([self.__dict__[cond] for cond in ['use_genes', 'use_alleles', 'use_chain'] if not clone_def[cond]])

    def __ge__(self, clone_def):
        if type(clone_def) == CloneDefinition:
            clone_def = clone_def.get_clone_def()

        if not set(clone_def['chains']).issubset(self.chains):
            return False

        if self.seq_type == 'aaseq' and clone_def['seq_type'] == 'ntseq':
            return False

        return all([self.__dict__[cond] for cond in ['use_genes', 'use_alleles', 'use_chain'] if clone_def[cond]])

class TCRseq(SequenceDefinition):
    """Class for TCR sequences (single chain)

    The class stores all provided sequence information for a TCR sequence
    (single chain -- only the TRB or TRA) and will return a standardized syntax
    string based on the SequenceDefinition that can be used as a hash for the
    sequence. Inherits all of the attributes of SequenceDefinition.

    Attributes
    ----------
    ntseq : str
        Nucleotide sequence of TCR CDR3 sequence (including conserved C and F)
    aaseq : str
        Amino acid sequence of TCR CDR3 sequence (including conserved C and F)
    chain : 'TRB' or 'TRA'
        Specifies what chain the sequence corresponds to
    v_genes : list
        List of V genes associated with the sequence
    j_genes : list
        List of J genes associated with the sequence
    v_alleles : list
        List of V alleles associated with the sequence
    j_alleles : list
        List of J alleles associated with the sequence

    Methods
    -------
    seq_rep(**kwargs)
        Returns standardized sequence string based on the sequence definition
        (modified by any **kwargs)
    set_chain(chain)
        Sets self.chain
    add_gene(gene)
        Adds genes/alleles to gene/allele lists
    """

    def __init__(self, ntseq = None, aaseq = None, **kwargs):

        SequenceDefinition.__init__(self, **kwargs)

        self.ntseq = ntseq
        if ntseq is not None:
            self.aaseq = nt2aa(ntseq)
            if aaseq is not None and aaseq != self.aaseq:
                raise SequenceMismatch(self.aaseq, aaseq)
        else:
            self.aaseq = aaseq
            self.seq_type = 'aaseq'

        self.chain = None
        self.v_genes = []
        self.j_genes = []
        self.v_alleles = []
        self.j_alleles = []

        for kw, kw_val in kwargs.items():
            if kw.lower() == 'chain':
                self.set_chain(kw_val.upper())
            elif kw.lower()[0] in 'vdj':
                self.add_gene(kw_val, gene_type = kw.lower())

    def set_chain(self, chain):
        """Sets chain of the sequence

        Parameters
        ----------
        chain : 'TRB' or 'TRA'
            Chain of sequence

        Raises
        ------
        ChainMismatch
            Error if chain is already specified and different than input

        Attributes modified
        -------------------
        self.chain

        """

        if self.chain is None:
            self.chain = chain
        elif chain != self.chain:
            raise ChainMismatch(self.chain, chain)

    def add_gene(self, gene_name, gene_type = '', reset = False):
        """Method to add genes/alleles to sequence info.

        This function will also infer/check the chain of the sequence based on
        provided gene names.

        Parameters
        ----------
        gene_name : str
            Gene/allele name
        gene_type : 'V' or 'J', optional
            Specified type of gene. The default is ''.
        reset : bool, optional
            Resets the gene list to only include provided gene.
            The default is False.

        Raises
        ------
        UnknownGene
        ChainMismatch

        Attributes Modified
        -------------------
        self.chain
        self.v_genes
        self.v_alleles
        self.j_genes
        self.j_alleles

        """

        if 'A' in gene_name.upper():
            self.set_chain('TRA')
        elif 'B' in gene_name.upper():
            self.set_chain('TRB')

        if gene_type.lower()[0] == 'v' or 'v' in gene_name.lower():
            if reset:
                self.v_genes = [gene_to_num_str(gene_name.lower(), 'v').split('*')[0]]
                self.v_alleles = [gene_to_num_str(gene_name.lower(), 'v')]
            else:
                self.v_genes.append(gene_to_num_str(gene_name.lower(), 'v').split('*')[0])
                self.v_alleles.append(gene_to_num_str(gene_name.lower(), 'v'))
        elif gene_type.lower()[0] == 'j' or 'j' in gene_name.lower():
            if reset:
                self.j_genes = [gene_to_num_str(gene_name.lower(), 'j').split('*')[0]]
                self.j_alleles = [gene_to_num_str(gene_name.lower(), 'j')]
            else:
                self.j_genes.append(gene_to_num_str(gene_name.lower(), 'j').split('*')[0])
                self.j_alleles.append(gene_to_num_str(gene_name.lower(), 'j'))
        else:
            raise UnknownGene(gene_name, gene_type)

    def seq_rep(self, **kwargs):
        """Returns standardized sequence string based on sequence definition.

        Returns
        -------
        str
            Sequence string

        """

        seq_def = self.get_seq_def()
        for kw, kw_val in kwargs.items():
            if kw in seq_def: seq_def[kw] = kw_val

        if seq_def['use_chain'] and self.chain is not None:
            s = [self.chain]
        else:
            s = []

        if seq_def['seq_type'] == 'ntseq' and self.ntseq is not None:
            s.append(self.ntseq)
        elif seq_def['seq_type'] == 'aaseq' and self.aaseq is not None:
            s.append(self.aaseq)

        if seq_def['use_alleles']:
            if len(self.v_alleles) > 0: s.append('/'.join(self.v_alleles))
            if len(self.j_alleles) > 0: s.append('/'.join(self.j_alleles))
        elif seq_def['use_genes']:
            if len(self.v_genes) > 0: s.append('/'.join(self.v_genes))
            if len(self.j_genes) > 0: s.append('/'.join(self.j_genes))

        #self.seq = self.seq_rep()
        return ','.join(s)

    def compare_full_sequence(self, tcr_seq):
        if type(self) != type(tcr_seq):
            raise ValueError
        return self.__dict__ == tcr_seq.__dict__

    def __repr__(self):
        return "TCRseq(%s)"%(self.seq_rep())

#%

class TcellClone(CloneDefinition):
    """Class for TCR clones (single or paired chains)

    The class stores all provided sequence information for TRA and TRB
    sequences and will return a standardized syntax string based on the
    CloneDefinition that can be used as a hash for the clone. Inherits all of
    the attributes of CloneDefinition.

    Attributes
    ----------
    TRA : list of TCRseq
        List of all TCRseq associated with the clone's TRA
    TRB : list of TCRseq
        List of all TCRseq associated with clone's TRB
    count : float
        Count associated with the clone. Generally cell count.

    Methods
    -------
    clone_rep(**kwargs)
        Returns standardized sequence string based on the clone definition
        (modified by any **kwargs)
    split_clone_rep(**kwargs)
        Returns all possible standardized sequence string with 1 TRA and 1 TRB
        based on the clone definition (modified by any **kwargs)
    load_TRB(**kwargs)
        Load TRB sequence
    load_TRA(**kwargs)
        Load TRA sequence
    """

    def __init__(self, **kwargs):

        CloneDefinition.__init__(self, **kwargs)

        self.TRB = []
        self.TRA = []
        self.count = 0

        for kw, kw_val in kwargs.items():
            if kw.upper() == 'TRB':
                self.load_TRB(**kw_val)
            elif kw.upper() == 'TRA':
                self.load_TRA(**kw_val)
            elif kw in self.__dict__:
                self.__dict__[kw] = kw_val

        if len(self.TRB) > 0 and len(self.TRA) == 0:
            self.chains = [c for c in self.chains if c!='TRA']
        elif len(self.TRA) > 0 and len(self.TRB) == 0:
            self.chains = [c for c in self.chains if c!='TRB']

    def load_TRB(self, **kwargs):
        """Load TRB sequence

        Parameters
        ----------
        **kwargs : keywords
            Input for TCRSeq.__init__ to specify TRB sequence

        Attributes modified
        -------------------
        self.TRB

        """
        seq_def = self.get_clone_def()
        kwargs['chain'] = 'TRB'
        for kw, kw_val in kwargs.items():
            seq_def[kw] = kw_val
        self.TRB.append(TCRseq(**seq_def))

    def load_TRA(self, **kwargs):
        """Load TRA sequence

        Parameters
        ----------
        **kwargs : keywords
            Input for TCRSeq.__init__ to specify TRA sequence

        Attributes modified
        -------------------
        self.TRA

        """
        seq_def = self.get_clone_def()
        kwargs['chain'] = 'TRA'
        for kw, kw_val in kwargs.items():
            seq_def[kw] = kw_val
        self.TRA.append(TCRseq(**seq_def))

    def clone_rep(self, **kwargs):
        """Returns standardized clone string based on clone definition.

        Returns
        -------
        str
            Clone string

        """

        clone_def = self.get_clone_def()
        for kw, kw_val in kwargs.items():
            if kw in clone_def: clone_def[kw] = kw_val

        return ';'.join([c_seq.seq_rep(**clone_def) for c_seq in sum([self.__dict__[c_chain] for c_chain in clone_def['chains']], [])])

    def split_clone_rep(self, **kwargs):
        """Returns all possible clone strings based on clone definition.

        If a clone has multiple TRA or TRB sequences this will enumerate the
        possible combinations.

        Returns
        -------
        tuple
            Tuple of clone strings

        """
        clone_def = self.get_clone_def()
        for kw, kw_val in kwargs.items():
            if kw in clone_def: clone_def[kw] = kw_val

        if len(clone_def['chains']) <= 1:
            return tuple([c_seq.seq_rep(**clone_def) for c_seq in self.__dict__[clone_def['chains'][0]]])
        else:
            return tuple([';'.join([c_seq1.seq_rep(**clone_def), c_seq2.seq_rep(**clone_def)]) for c_seq1 in self.__dict__[clone_def['chains'][0]] for c_seq2 in self.__dict__[clone_def['chains'][1]]])

    def __repr__(self):
        return "TcellClone(%s)"%(self.clone_rep())


import RPi.GPIO as GPIO
from mfrc522 import SimpleMFRC522
import datetime
import time
import csv

with open('users/users.csv', mode='r') as infile:
    csv_reader = csv.reader(infile)
    users = {int(rows[0]):rows[1] for rows in csv_reader}

while(True):
  reader = SimpleMFRC522()
  print("scan badge to login")
  try:
    id_, text = reader.read()
    now = datetime.datetime.now()

    if id_ in users:
      print(f"\n\tHello, {users[id_]}")
      print(f"\tScanned {users[id_]} at {now}\n")
      time.sleep(2)
    elif id:
      print("\n\tUser not recognized")
      time.sleep(2)

  finally:
    print("\tResetting RFID reader...")
    GPIO.cleanup()


"""
#  setting the environment for each nodes (agent)
#  get the log data from apis-emulator for states

@author: Qiong
"""

import logging.config
import time

import numpy as np
# import random
# import gym
# from gym.utils import seeding

logger = logging.getLogger(__name__)

import global_var as gl
import config as conf
import requests, json

from createScenario import CreateScenario

# Data loading
# get log data for states
host = conf.b_host
port = conf.b_port
# url = "http://0.0.0.0:4390/get/log"
URL = "http://" + host + ":" + str(port) + "/get/log"

# dicts of states for all houses
pvc_charge_power = {}
ups_output_power = {}
p2 = {}  # powermeter.p2, Power consumption to the power storage system [W]
rsoc = {}
wg = {}  # meter.wg, DC Grid power [W]
wb = {}  # meter.wb, Battery Power [W]

pv_list = []
load_list = []
p2_list = []

class APIS():
    def __init__(self, memory_size=10000):
        self.memory_size = memory_size
        self.memory = np.zeros((self.memory_size, 4))

    def CreateSce(self):
        newSce = CreateScenario()
        newSce.write_json()

        # if __name__ == "__main__":
        #     interval = 60 * 60  # every 60 * 60s
        #     command = createJson()
        #     run(interval, command)

    def store_value(self, pvc, load, p2, rsoc):
        if not hasattr(self, 'memory_counter'):
            self.memory_counter = 0
        store_values = np.hstack((pvc, load, p2, rsoc))
        index = self.memory_counter % self.memory_size
        self.memory[index, :] = store_values
        self.memory_counter += 1

# House Model, step function (reward)

class House():

    def __init__(self):

        self.action_request_space = np.linspace(0.2, 0.9, 8).tolist()
        self.action_accept_space = np.linspace(0.2, 0.9, 8).tolist()

        # list of possible actions
        # reward

    # def seed(self, seed=None):
    #     self.np_random, seed = seeding.np_random(seed)
    #     return [seed]

    def step(self, state, action_request, action_accept):
        # how actions changes the states?

        # current_pv = state[0]
        # current_load = state[1]
        # current_p2 = state[2]
        # current_rsoc = state[3]
        # current_rsoc_ave = state[4]

        current_pvc_e001, current_load_e001, current_p2_e001, current_rsoc_e001, current_rsoc_ave = self.state
        # current_pvc = gl.oesunits[ids]["emu"]["pvc_charge_power"]
        # current_rsoc = core.rsocUpdate()

        output_data = requests.get(URL).text
        output_data = json.loads(output_data)  # dict

        rsoc_list = []

        for ids, dict_ in output_data.items():  # ids: E001, E002, ... house ID
            # print('the name of the dictionary is ', ids)
            # print('the dictionary is ', dict_)
            # when ids is "E001" (change to other house ID for other houses)
            pvc_charge_power[ids] = output_data[ids]["emu"]["pvc_charge_power"]
            ups_output_power[ids] = output_data[ids]["emu"]["ups_output_power"]
            p2[ids] = output_data[ids]["dcdc"]["powermeter"]["p2"]
            rsoc[ids] = output_data[ids]["emu"]["rsoc"]
            wg[ids] = output_data[ids]["dcdc"]["meter"]["wg"]
            wb[ids] = output_data[ids]["dcdc"]["meter"]["wb"]

            rsoc_list.append(rsoc[ids])
            # refresh every 60 seconds
            # print("\n")
            # time.sleep(60)

            # States  pvc_charge_power[ids], for house E001
            if ids == "E001":
                current_pvc_e001 = np.array([pvc_charge_power["E001"]])
                current_load_e001 = np.array([ups_output_power["E001"]])
                current_p2_e001 = np.array([p2["E001"]])
                current_rsoc_e001 = np.array([rsoc["E001"]])

                current_all_e001 = np.concatenate([current_pvc_e001,
                                                   current_load_e001,
                                                   current_p2_e001,
                                                   current_rsoc_e001], axis=-1)
                # print(current_all_e001, type(current_all_e001))  # [39.14 575.58 734.    29.98] E001

        # print(rsoc)
        # {'E001': 29.98, 'E002': 29.99, 'E003': 29.98, 'E004': 29.99}
        current_rsoc_ave = np.mean(rsoc_list)  # get average rsoc of this community
        # print(rsoc_ave)
        self.state = np.concatenate([current_all_e001, np.array([current_rsoc_ave])], axis=-1)

        reward = current_p2_e001
        # done = time.sleep(5)  # time, e.g., one hour(time.sleep(60*60)) or given #EPI

        return np.array(self.state, dtype=np.float32), reward,  {}  # done

    def reset(self):
        # reset the states according to standard.json file (../apis-emulator/jsontmp)
        # all values are the same to each house
        # super().reset(seed=seed)

        # init state
        pvc_charge_power = np.array([0.])
        ups_output_power = np.array([0.])
        p2 = np.array([0.])
        rsoc = np.array([50.])
        # wg = np.array([0])
        # wb = np.array([-4.5])
        rsoc_ave = np.array([50.])  # average rsoc in the same community

        # self.state = np.array([self.state])
        self.state = np.concatenate([pvc_charge_power, ups_output_power, p2, rsoc, rsoc_ave], axis=-1)

        # return np.array(self.state, dtype=np.float32)
        return np.array(self.state, dtype=np.float32)


from ..utils import get_url_as_json

ARTICLE_URL = 'http://ci.nii.ac.jp/opensearch/search?count=200&start={offset}&lang=ja&journal={magazine}&issn=&publisher={publisher}&year_from={year_from}&year_to={year_to}&format=json&appid=upmkTSWtpQK7f9LqGpTg'

def cinii_articles(magazine, publisher, year_from, year_to):

    articles = []
    offset = 0

    while True:
        results = getUrlAsJson(ARTICLE_URL.format(
            offset=offset,
            magazine=magazine,
            publisher=publisher,
            year_from=year_from,
            year_to=year_to
        ))

        if "items" in results["@graph"][0]:
            article_list = results["@graph"][0]["items"]
        else:
            article_list = []
        total = int(results["@graph"][0]["opensearch:totalResults"])

        for article in article_list:
            title = article["title"]
            date = article["dc:date"]
            year = int(date.split("-")[0])
            try:
                month = int(date.split("-")[1])
            except:
                month = 0
            link = article["@id"]
            authors = [{"name": author["@value"], "id": ""} for author in article["dc:creator"]]
            try:
                vol = article["prism:number"]
            except:
                vol = None
            try:
                page_from = article["prism:startingPage"]
            except:
                page_from = None
            try:
                page_to = article["prism:endingPage"]
            except:
                page_to = None

            articles.append({
                "title": title,
                "magazine": magazine,
                "publisher": publisher,
                "volume": vol,
                "authors": authors,
                "year": year,
                "month": month,
                "page_from": page_from,
                "page_to": page_to,
                "source": "cinii",
                "link": link
            })

        offset += 200
        if offset > total:
            break

    return articles


from os.path import exists
from pathlib import Path

import pandas as pd
import requests
from bs4 import BeautifulSoup
from thefuzz import fuzz

from constants import LOW_THRESHOLD
from models import Alias
from settings import URL
from utils import parse_day_to_date, get_current_datetime

def get_html_text():
    """
    Get the HTML content as plain text
    :return: A string html
    """
    r = requests.get(URL)
    return r.text

def get_channels(html_text):
    """
    Get the channels of each event from an HTML as plain text
    :param html_text: The HTML as plain text
    :return: A List of string, where each below to a Channel.
    """
    soup = BeautifulSoup(html_text, "html.parser")
    channels_elements = soup.findAll("img", class_="di-tv-channel-thumb", alt=True)
    channels = [channel["alt"] for channel in channels_elements]
    return channels

def process_df_using_html(df, html):
    """
    Drop unused columns from a dataframe
    :param df: A Pandas DataFrame
    :param html: A HTML body
    :return: A Pandas DataFrame
    """
    df_cpy = df.copy()
    df_cpy = df_cpy.drop(["PARTIDO.1", "PARTIDO.2", "PARTIDO.3"], axis=1)
    channels = get_channels(html)
    df_cpy["FECHA"] = df_cpy["FECHA"].map(
        lambda fecha: parse_day_to_date(int(fecha[-2:]))
    )
    df_cpy["CANAL"] = channels
    return df_cpy

def get_events_df():
    """
    Get a Pandas Dataframe with all the events available in the target WebSite.
    :return: A Pandas Dataframe.
    """
    current_date = get_current_datetime().date()
    df_base_path = Path("media")
    df_base_path.mkdir(exist_ok=True)
    df_path = df_base_path / f"{current_date}.pkl"

    if not exists(df_path):
        html = get_html_text()
        raw_df = pd.read_html(html)[0]
        df = process_df_using_html(raw_df, html)
        # Saving the Dataframe to avoid unnecessary requests over the Website.
        df.to_pickle(df_path)
        return df
    else:
        df = pd.read_pickle(df_path)
        return df

def get_events_df_per_date(df, date):
    """
    Get a Pandas Dataframe where each row has as date the date given
    :param df: The Pandas Dataframe to filter
    :param date: A Datetime.Date to filter
    :return: A Pandas Dataframe filtered by the date
    """
    df_filtered = df[df["FECHA"] == date]
    return df_filtered

def filter_events_using_substring(df, txt, threshold=LOW_THRESHOLD):
    """
    Filter the rows of a Pandas Dataframe where some attributes contain the substring given
    :param df: The Pandas Dataframe to filter
    :param txt: The substring to search
    :param threshold: The minimum score allowed to match an approximate search
    :return: A Pandas Dataframe where each rows contain the substring given or approximate.
    """
    df_cpy = df.copy()
    df_with_teams = add_teams(df_cpy)
    df_with_teams["Score"] = df_with_teams.apply(
        lambda entry: calculate_score(entry, txt), axis=1
    )
    df_approximate = get_approximate_matches(df_with_teams, threshold=threshold)
    df_substring = get_matches_are_substring(df_with_teams, txt)
    matches = pd.concat([df_substring, df_approximate])
    events = matches.drop_duplicates()
    return events

def add_teams(df):
    """
    Add teams to DataFrame
    :param df: The Pandas Dataframe
    :return: A pandas.DataFrame
    """
    df_cpy = df.copy()
    df_cpy["team_1"] = df_cpy.apply(lambda r: split_by_team(r)[0], axis=1)
    df_cpy["team_2"] = df_cpy.apply(lambda r: split_by_team(r)[1], axis=1)
    return df_cpy

def split_by_team(r):
    """
    Split a row by team
    :param r: The Pandas Dataframe row
    :return: A list
    """
    event = r["PARTIDO"]
    split = list(map(lambda team: team.strip(), event.split("v/s")))
    split = split if len(split) == 2 else [event, event]
    return split

def get_matches_are_substring(df, txt):
    """
    Filter the rows of a Pandas Dataframe where some attributes contain the substring given
    :param df: The Pandas Dataframe to filter
    :param txt: The substring to search
    :return: A Pandas Dataframe where each rows contain the substring given.
    """
    df_cpy = df.copy()
    return df_cpy[
        df_cpy["PARTIDO"].str.contains(txt, case=False)
        | df_cpy["COMPETENCIA"].str.contains(txt, case=False)
        | df_cpy["CANAL"].str.contains(txt, case=False)
    ]

def get_approximate_matches(df, threshold):
    """
    Filter the rows of a Pandas Dataframe with a threshold for a specific column
    :param df: The Pandas Dataframe to filter
    :param threshold: The minimum value to filter
    :return: A Pandas Dataframe filtered.
    """
    df_cpy = df.copy()
    return df_cpy[df_cpy["Score"] >= threshold]

def calculate_score(row, txt):
    """
    Calculate the coincidence of the row with a substring
    :param row: The Pandas Dataframe row
    :param txt: The substring to search
    :return: An int that represents the match score between the row and the given text.
    """
    fields_and_weight = [
        {"field": "PARTIDO", "weight": 1},
        {"field": "COMPETENCIA", "weight": 1},
        {"field": "CANAL", "weight": 1},
        {"field": "team_1", "weight": 1},
        {"field": "team_2", "weight": 1},
    ]

    scores = [
        fuzz.partial_ratio(txt.lower(), row[entry["field"]].lower()) * entry["weight"]
        for entry in fields_and_weight
    ]

    return max(scores)

async def may_get_team_name_from_user_and_alias(user_id: str, alias: str) -> str | None:
    try:
        alias = await Alias.objects.get(user_id=user_id, alias=alias)
        return alias.team_name
    except Exception:  # noqa
        return None


import soundcard as sc
import soundfile as sf
from matplotlib import pyplot as plt
import wave
import io

import pygame
OUTPUT_FILE_NAME = "out.wav"    # file name.
SAMPLE_RATE = 48000              # [Hz]. sampling rate.
RECORD_SEC = 3                  # [sec]. duration recording audio.

pygame.init()
my_font = pygame.font.SysFont('Times New Roman', 30)

win = pygame.display.set_mode((500, 400))
pygame.display.set_caption("Visualizer")
my_font = pygame.font.SysFont('Times New Roman', 30)

text_surface = my_font.render('Some Text', False, (0, 0, 0))
win.blit(text_surface, (0,0))

dataM = None

with sc.get_microphone(id=str(sc.default_speaker().name), include_loopback=True).recorder(samplerate=SAMPLE_RATE) as mic:
    # record audio with loopback from default speaker.
    data = mic.record(numframes=SAMPLE_RATE*RECORD_SEC)
    dataM = data
    #print(data.size)
    #print(data)
    # change "data=data[:, 0]" to "data=data", if you would like to write audio as multiple-channels.
    sf.write(file=OUTPUT_FILE_NAME, data=data[:, 0], samplerate=SAMPLE_RATE)

print(dataM)
for x in range(1, 1000):
    #print(dataM[x][1])
    plt.plot(dataM[x][1])

# with io.BytesIO() as wav_file:
#     wav_writer = wave.open(wav_file, "wb")
#     try:
#         wav_writer.setframerate(SAMPLE_RATE)
#         wav_writer.setsampwidth(3)
#         wav_writer.setnchannels(2)
#         wav_writer.writeframes(data)
#         wav_data = wav_file.getvalue()
#     finally:
#         wav_writer.close()

sf.write(file="remade.wav", data=dataM, samplerate=SAMPLE_RATE)
plt.plot(dataM)
plt.show()


# coding=utf-8

"""Definitions about the age field group."""

from safe.definitions.fields import (
    infant_ratio_field,
    child_ratio_field,
    youth_ratio_field,
    adult_ratio_field,
    elderly_ratio_field,
    infant_count_field,
    child_count_field,
    youth_count_field,
    adult_count_field,
    elderly_count_field,
    infant_displaced_count_field,
    child_displaced_count_field,
    youth_displaced_count_field,
    adult_displaced_count_field,
    elderly_displaced_count_field)
from safe.utilities.i18n import tr

__copyright__ = "Copyright 2017, The InaSAFE Project"
__license__ = "GPL version 3"
__email__ = "info@inasafe.org"
__revision__ = '1a0dd4852cc92d080bf32eb73bc0487259083a8c'

age_ratio_group = {
    'key': 'age_ratio_group',
    'name': tr('Age Ratio'),
    'description': tr(
        'Demographic breakdown to use for displaced population based on age '
        'groups. Age ratio groupings are used when there is a vector '
        'aggregation layer that contains detailed demographic information ('
        'as ratios) about the population living in each administrative or '
        'census area. These ratios are then applied to the count of displaced '
        'population per aggregation area to provide a more detailed break '
        'down of the number of people displaced in each age group.'),
    'fields': [
        infant_ratio_field,
        child_ratio_field,
        youth_ratio_field,
        adult_ratio_field,
        elderly_ratio_field
    ],
    # Exclusive = False: able to put same layer's field to some fields
    # Exclusive = True: only able to put a layer's field to a field
    'exclusive': False,
    'notes': [],
    'constraints': {}
}
age_count_group = {
    'key': 'age_count_group',
    'name': tr('Age Count'),
    'description': tr(
        'Demographic breakdown to use for displaced population based on age '
        'groups. Age count groupings are used when there is a vector '
        'population dataset that contains detailed demographic information ('
        'as counts) about the population living in each administrative or '
        'census area.'
    ),
    'fields': [
        infant_count_field,
        child_count_field,
        youth_count_field,
        adult_count_field,
        elderly_count_field
    ],
    # Exclusive = False: able to put same layer's field to some fields
    # Exclusive = True: only able to put a layer's field to a field
    'exclusive': False,
    'notes': []
}
age_displaced_count_group = {
    'key': 'age_displaced_count_group',
    'name': tr('Age Displaced Count'),
    'header_name': tr('Age'),
    'description': tr(
        'Demographic breakdown produced for displaced population based on age '
        'groups. These demographic concepts provide a detailed break '
        'down of the number of people displaced in each age group.'),
    'fields': [
        infant_displaced_count_field,
        child_displaced_count_field,
        youth_displaced_count_field,
        adult_displaced_count_field,
        elderly_displaced_count_field
    ],
    # Exclusive = False: able to put same layer's field to some fields
    # Exclusive = True: only able to put a layer's field to a field
    'exclusive': False,
    'notes': [],
    'section_unavailable_note:': tr(
        'No age ratios provided, no demographic breakdown for age carried out.'
    )
}


import tkinter as tk
from main_window.switcher import Switch_tab
from main_window.db_helper import Db_helper

class Clear_table():
    def clear(self):
        tab = self.tab.got_tab()
        if tab == "all":
            self.helper.execute_query_insert(f"DELETE FROM orders")
        else:
            self.helper.execute_query_insert(f"DELETE FROM orders WHERE id_sell_table = {tab}")
        self.self_Text.delete()
        self.self_Summ.delete()

    def __init__(self, frame_window, self_text, self_summ):
        self.frame_window = frame_window
        self.clear = tk.Button(self.frame_window, text="Clear", width=15, height=3, command=self.clear)
        self.clear.place(x=130, y=410)
        self.self_Text = self_text
        self.self_Summ = self_summ
        self.tab = Switch_tab()
        self.helper = Db_helper()