import os
import psutil
import re
import shutil
import subprocess
import sys
import tarfile
import zipfile

def mkdir(path):
	if not os.path.isdir(path):
		os.makedirs(path)

def prun(*cmd):
	subprocess.check_call(filter(lambda s: s and not s.isspace(), cmd))

def getVersion():
	with open('src/server/server.h', 'r') as fh:
		return re.search(r'char\s*commonVersion\[.*\]\s*=\s*"(.*)"', fh.read()).group(1)

def getThreads():
	cnt = sys.argv[2] if len(sys.argv) > 2 else str(psutil.cpu_count(False))
	return str(cnt) if cnt else '1'

def writeZip(odir):
	with zipfile.ZipFile(odir + '.zip', 'w', zipfile.ZIP_DEFLATED) as zh:
		for root, dirs, files in os.walk(odir):
			for ft in files:
				zh.write(os.path.join(root, ft))

def writeTar(odir):
	with tarfile.open(odir + '.tar.gz', 'w:gz') as th:
		th.add(odir)

def expCommon(odir, pdir, regGL = True, copyLicn = True, message = ''):
	mkdir(odir)
	os.chdir(odir)
	if regGL:
		shutil.copytree(os.path.join('..', pdir, 'data'), 'data')
	if copyLicn:
		shutil.copytree(os.path.join('..', pdir, 'licenses'), 'licenses')
	shutil.copytree(os.path.join('..', 'doc'), 'doc')
	os.chdir('..')

	with open('README.md', 'r') as fh:
		txt = fh.read()
	txt = re.sub(r'##\sBuild\s*', message, txt, flags = re.M | re.S)
	txt = re.sub(r'The\sCMakeLists\.txt.*', '', txt, flags = re.M | re.S)
	if regGL:
		txt = re.sub(r',\slibjpeg', '', txt, flags = re.M | re.S)
	with open(os.path.join(odir, 'README.md'), 'w') as fh:
		fh.write(txt)

def expAndroid(odir):
	expCommon(odir, os.path.join('android', 'app'), False)
	shutil.copy(os.path.join('android', 'app', 'release', 'app-release.apk'), os.path.join(odir, 'Thrones.apk'))
	writeZip(odir)

def expMac(odir, pdir):
	expCommon(odir, pdir, copyLicn = False)
	shutil.copy(os.path.join(pdir, 'Thrones.app'), odir)
	shutil.copy(os.path.join(pdir, 'server'), odir)
	for root, dirs, files in os.walk(odir):
		for ft in files:
			if ft == '.DS_Store':
				os.remove(os.path.join(root, ft))
	writeZip(odir)

def expLinux(odir, pdir):
	expCommon(odir, pdir)
	shutil.copy(os.path.join(pdir, 'thrones'), odir)
	shutil.copy(os.path.join(pdir, 'server'), odir)
	shutil.copy(os.path.join(pdir, 'thrones.desktop'), odir)
	writeTar(odir)

def expWeb(odir, pdir):
	expCommon(odir, pdir, False)
	shutil.copy(os.path.join(pdir, 'thrones.html'), odir)
	shutil.copy(os.path.join(pdir, 'thrones.js'), odir)
	shutil.copy(os.path.join(pdir, 'thrones.wasm'), odir)
	shutil.copy(os.path.join(pdir, 'thrones.data'), odir)
	shutil.copy(os.path.join(pdir, 'thrones.png'), odir)
	writeZip(odir)

def expWin(odir, pdir, msg):
	expCommon(odir, pdir, message = msg)
	shutil.copy(os.path.join(pdir, 'Thrones.exe'), odir)
	shutil.copy(os.path.join(pdir, 'Server.exe'), odir)
	for ft in ['SDL2', 'SDL2_image', 'SDL2_ttf', 'libpng16-16', 'libfreetype-6', 'zlib1']:
		shutil.copy(os.path.join(pdir, ft + '.dll'), odir)
	writeZip(odir)

def genProject(pdir, pref = '', *gopt):
	mkdir(pdir)
	os.chdir(pdir)
	prun(pref, 'cmake', '..', *gopt)

def genLinux(pdir, target = '', *gopt):
	genProject(pdir, '', *gopt)
	prun('make', '-j', getThreads(), target)

def genWeb(pdir):
	wglDir = 'build_wgl'
	genLinux(wglDir, 'assets', '-DOPENGLES=1')
	os.chdir('..')
	genProject(pdir, 'emcmake')

	wglRel = os.path.join('..', wglDir, 'bin')
	os.rename(os.path.join(wglRel, 'data'), 'data')
	os.rename(os.path.join(wglRel, 'licenses'), 'licenses')
	os.rename(os.path.join('data', 'thrones.png'), 'thrones.png')
	prun('emmake', 'make', '-j', getThreads())

if __name__ == '__main__':
	if len(sys.argv) < 2:
		print("usage: " + os.path.basename(__file__) + " <action>")
		quit()

	lnxDir = 'build_lnx'
	glesDir = 'build_gles'
	webDir = 'build_web'
	win32Dir = 'build_win32'
	win64Dir = 'build_win64'
	if sys.argv[1] == 'android':
		expAndroid('Thrones_' + getVersion() + '_android')
	elif sys.argv[1] == 'linux':
		expLinux('Thrones_' + getVersion() + '_linux64', os.path.join(lnxDir, 'bin'))
	elif sys.argv[1] == 'gles':
		expLinux('Thrones_' + getVersion() + '_gles64', os.path.join(glesDir, 'bin'))
	elif sys.argv[1] == 'web':
		expWeb('Thrones_' + getVersion() + '_web', webDir)
	elif sys.argv[1] == 'mac':
		exprMac('Thrones_' + getVersion() + '_mac64', 'build')
	elif sys.argv[1] == 'win':
		expWin('Thrones_' + getVersion() + '_win32', os.path.join(win32Dir, 'bin'), 'To run the program you need to have the Microsoft Visual C++ Redistributable 2019 32-bit installed.  \n')
		expWin('Thrones_' + getVersion() + '_win64', os.path.join(win64Dir, 'bin'), 'To run the program you need to have the Microsoft Visual C++ Redistributable 2019 64-bit installed.  \n')
	elif sys.argv[1] == 'glinux':
		genLinux(lnxDir)
	elif sys.argv[1] == 'ggles':
		genLinux(glesDir, '', '-DOPENGLES=1')
	elif sys.argv[1] == 'gweb':
		genWeb(webDir)
	elif sys.argv[1] == 'gwin':
		genProject(win32Dir, '', '-G', '"Visual Studio 16"', '-A', '"Win32"')
		genProject(win64Dir, '', '-G', '"Visual Studio 16"',  '-A', '"x64"')
	else:
		print('unknown action')


import pandas

import glob
import os
import fnmatch

#list_of_files = glob.glob("/home/people/andras/Data/VCF/VCF_files/./*.vcf") # creating list of all .txt files in current directory
list_of_files = os.listdir("/home/people/andras/Data/VCF/VCF_files/VCF_filtered/bgzip/Ped_fam_affec_noaffec")

pattern = "*.txt"

os.mkdir("QSUB_bcftools")
#os.mkdir("/home/people/andras/Data/VCF/VCF_files/VCF_filtered/bgzip/merge_files")
#os.mkdir("/home/people/andras/Data/VCF/VCF_files/VCF_filtered")

for file_name in list_of_files:
	if fnmatch.fnmatch(file_name, pattern):

		Type = file_name.split(".") # splitting file_name separated by "."
		x = Type[0] # selecting first part of name
		z = x.split("_")  #splitting file_name separated by "_"
		p = z[0] # selecting first word of name
		y = p.replace("/", "")
		#os.chdir("/home/people/andras/Data/VCF/VCF_files/VCF_filtered/bgzip/Ped_fam_affec_noaffec")
		#df = pandas.read_csv(file_name, sep='\t', lineterminator='\n', keep_default_na=True, dtype = "object") # importing file with the name "file_name"

		#df["End"] = ".vcf.gz"
		#df2 = df["merge"] = df[["Decode_ID", "End"]].apply(lambda x: ''.join(x), axis=1)

		#os.chdir("/home/people/andras/Data/VCF/VCF_files/VCF_filtered/bgzip/merge_files")
		#df2.to_csv(file_name.replace("txt" , "merge_file.txt"), sep="\t", index=False, na_rep="NA", header=False) # exporting file with
		#s.chdir("/home/people/andras")

		FO = open("QSUB_bcftools/"+ y + ".qsub",'w') #new file created
		FO.write("#!/bin/sh \n")

		FO.write("\n")
		FO.write("module load bcftools/1.6 \n")
		FO.write("\n")
		FO.write("\n")
		FO.write("cd /home/people/andras/Data/VCF/VCF_files/VCF_filtered/bgzip \n")

		FO.write("\n")
		FO.write("bcftools merge -l merge_files/" +  y  + ".merge_file.txt -O v -o  " + y + ".vcf \n")
		FO.write("\n")
		FO.write("sed 's/\.\/\./0\/0/g' " + y + ".vcf > " + y + ".sed.vcf \n")
		FO.write("\n")

		FO.write("grep ^# " + y + ".sed.vcf > " + y + ".sed.header.vcf \n")

		FO.write("\n")

		FO.write("grep -v ^# " + y + ".sed.vcf | awk '{print $1, $2-1, $2,$3\"|\"$4\"|\"$5\"|\"$6\"|\"$7\"|\"$8\"|\"$9\"|\"$10\"|\"$11\"|\"$12\"|\"$13\"|\"$14\"|\"$15\"|\"$16\"|\"$17\"|\"$18\"|\"$19\"|\"$20\"|\"$21\"|\"$22}' > " + y + ".sed.bed \n")
		FO.write("\n")

		FO.write("sed -e 's/ [ ]*/\\t/g' " + y + ".sed.bed > " + y + ".sed.sed.bed \n")

		FO.write("\n")

		FO.write("./liftOver " + y + ".sed.sed.bed /home/people/andras/Data/VCF/VCF_files/VCF_filtered/merge/hg38ToHg19.over.chain.gz " + y + ".sed.sed.hg19.bed " + y + ".sed.sed.unlifted.bed \n")

		FO.write("\n")

		FO.write("awk '{print $1, $3, $4}' " + y + ".sed.sed.hg19.bed | sed -e 's/|/\\t/g' | sed 's/  */\\t/g' > "+ y + ".sed.sed.hg19.sed.vcf \n")

		FO.write("\n")
		FO.write("cat " + y + ".sed.header.vcf " + y + ".sed.sed.hg19.sed.vcf > " + y + "hg19.vcf \n")
		FO.write("\n")

		FO.close()


# Code to merge two different sorted arrays

def mergeArrays(arr1, arr2):
    n1 = len(arr1)
    n2 = len(arr2)
    arr3 = [None] * (n1 + n2)
    i,j,k = 0,0,0
    while i < n1 and j < n2:
        if arr1[i] < arr2[j]:
            arr3[k] = arr1[i]
            k = k + 1
            i = i + 1
        else:
            arr3[k] = arr2[j]
            k = k + 1
            j = j + 1
    while i < n1:
        arr3[k] = arr1[i];
        k = k + 1
        i = i + 1
    while j < n2:
        arr3[k] = arr2[j];
        k = k + 1
        j = j + 1
    for i in range(n1 + n2):
        print(str(arr3[i]), end = " ")

# We can also modify this section to take manual array inputs
arr1 = [1, 3, 5, 7]
arr2 = [2, 4, 6, 8]
mergeArrays(arr1, arr2);


from flask import Flask, abort, jsonify, request
import sqlite3 as sql

app = Flask(__name__)

@app.route('/tasks', methods=['GET'])
def get_tasks():
    conn = sql.connect('database.db')
    cur = conn.cursor()
    cur.execute("select * from Tasks")
    rows = cur.fetchall()
    task_list = list()
    for row in rows:
        each_task = dict()
        each_task['Uri'] = row[0]
        each_task['Title'] = row[1]
        each_task['Description'] = row[2]
        each_task['Done'] = row[3]
        task_list.append(each_task)
    conn.close()
    return jsonify(task_list)

@app.route('/tasks', methods=['POST'])
def create_task():
    if not request.json or not 'Uri' in request.json:
        abort(400)
    conn = sql.connect('database.db')
    with conn:
        cur = conn.cursor()
        cur.execute("INSERT INTO Tasks (Uri,Title,Description,Done) VALUES (?,?,?,?)"
        , (request.json['Uri'], request.json['Title'], request.json.get('Description', ''), 0))
        conn.commit()
    return jsonify({'Message': 'Insert successfully'}), 201

@app.route('/tasks/<task_id>', methods=['DELETE'])
def delete_task(task_id):
    conn = sql.connect('database.db')
    cur = conn.cursor()
    try:
        cur.execute("DELETE FROM Tasks WHERE Uri=(?)", (task_id,))
        msg = "delete sucessfully!"
        conn.commit()
    except:
        msg = "Something worong!"
    finally:
        conn.close()
        return jsonify({'Message': msg})

@app.route('/tasks/<task_id>', methods=['PUT'])
def update_task(task_id):
    conn = sql.connect('database.db')
    with conn:
        cur = conn.cursor()
        cur.execute("UPDATE Tasks SET Title = (?) WHERE Uri = (?)", (request.json['Title'],task_id))
    return jsonify({'Message': 'Update successfully'})
    # You need to update Title and Description column.
    # Then, return your response and correct response code to your client!

@app.route('/tasks/<task_id>', methods=['GET'])
def get_a_task(task_id):
    conn = sql.connect('database.db')
    cur = conn.cursor()
    cur.execute("select * from Tasks WHERE Uri = (?)", (task_id,))
    row = cur.fetchone()
    each_task = dict()
    each_task['Uri'] = row[0]
    each_task['Title'] = row[1]
    each_task['Description'] = row[2]
    each_task['Done'] = row[3]
    return jsonify(each_task)

if __name__ == '__main__':
    app.run(debug=True)


"""Advanced 3D Polylidar Example
This example shows the limitations of Polylidar in extracting planes from 3D point clouds.
The main limitations are:
    1. Only planes that have normals at roughly [0,0,1] are extracted.
       Rotate point cloud prior to sending to Polylidar to resolve issue.
    2. Planes can not be on top of eachother. More precisely ponint cloud DATA can not be on top of eachother.
       Resolve by paritioning the planes into seperate point clouds.
       An example of two planes on top of eachtother where the DATA is NOT on top of eachother is on basic3d.py

Polylidar was primarily designed for finding flat surfaces on rooftops from downward facing sensors
where these issues are not as prevalent as seen in this advanced example.
"""
import time
import math

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

from polylidar import extractPlanesAndPolygons
from polylidarutil import (plot_polygons_3d, generate_3d_plane, set_axes_equal, plot_planes_3d,
                           scale_points, rotation_matrix, apply_rotation, set_up_axes, COLOR_PALETTE)

# arguments to polylidar used throughout this example
polylidar_kwargs = dict(alpha=0.0, lmax=1.0,
                        minTriangles=20, zThresh=0.1, normThresh=0.98)
np.random.seed(1)
# generate random plane
plane = generate_3d_plane(bounds_x=[0, 10, 0.5], bounds_y=[
                          0, 10, 0.5], holes=[], height_noise=0.02, planar_noise=0.02)
# Generate 2 walls
box_side = generate_3d_plane(bounds_x=[0, 10, 0.5], bounds_y=[
                             0, 10, 0.5], holes=[], height_noise=0.02, planar_noise=0.02)
rm = rotation_matrix([0, 1, 0], -math.pi/2.0)
box_side_left = apply_rotation(rm, box_side) + [0, 0, 0]  # first wall
box_side_right = apply_rotation(rm, box_side) + [10, 0, 0]  # second wall
# All points joined together
points = np.concatenate((plane, box_side_left, box_side_right))

# create figure and axes
fig, ax = plt.subplots(figsize=(10, 10), nrows=1, ncols=1, num='Base Frame',
                       subplot_kw=dict(projection='3d'))
# plot points
ax.scatter(*scale_points(points), c='k', s=0.4)
set_up_axes(fig, ax)
plt.show(block=False)
print("Raw Point Cloud of ground floor and two walls (Base Frame). Don't close figure.")
print("This example will show how to extract all three planes.")
print("During this process you will learn the limitations of Polylidar for 3D point clouds and how to work around them.")
input("Press Enter to Continue: ")
print("")

# extract ground plane
print("Extracting ground plane from raw point cloud. Bottom Plane Extracted")
delaunay, planes, polygons = extractPlanesAndPolygons(
    points,  **polylidar_kwargs)
triangles = np.asarray(delaunay.triangles).reshape(
    int(len(delaunay.triangles) / 3), 3)
plot_planes_3d(points, triangles, planes, ax)
plot_polygons_3d(points, polygons, ax, color=COLOR_PALETTE[0])
plt.show(block=False)
input("Press Enter to Continue: ")
print("")

# Rotate Point Cloud for walls
print("The walls must be rotated to be extracted such that their normal is (0,0,1). Rotated Frame.")
fig2, ax2 = plt.subplots(figsize=(10, 10), nrows=1, ncols=1,  num='Rotated Frame',
                         subplot_kw=dict(projection='3d'))
# Transpose provides reverse rotation
pc_rot = apply_rotation(rm.T, points)
scatter = ax2.scatter(*scale_points(pc_rot), c='k', s=0.4)
set_up_axes(fig, ax2)
plt.show(block=False)
input("Press Enter to Continue: ")
print("")

# Seperate point clouds
print("Unfortunately these 2 walls will interefere with eachother when projected to the z=0 xy-Plane.")
print("They must be seperated into two different point clouds (orange/green)")
ax2.clear()
pc_top = pc_rot[pc_rot[:, 2] > -5.0, :]
pc_bottom = pc_rot[pc_rot[:, 2] < -5.0, :]
scatter1 = ax2.scatter(*scale_points(pc_top), c=[COLOR_PALETTE[1]], s=0.4)
scatter2 = ax2.scatter(*scale_points(pc_bottom), c=[COLOR_PALETTE[2]], s=0.4)
plt.show(block=False)

input("Press Enter to Continue: ")
print("")
# Extract planes from top and bottom
delaunay_top, planes_top, polygons_top = extractPlanesAndPolygons(
    pc_top,  **polylidar_kwargs)
triangles_top = np.asarray(delaunay_top.triangles).reshape(
    int(len(delaunay_top.triangles) / 3), 3)
delaunay_bot, planes_bot, polygons_bot = extractPlanesAndPolygons(
    pc_bottom,  **polylidar_kwargs)
triangles_bot = np.asarray(delaunay_bot.triangles).reshape(
    int(len(delaunay_bot.triangles) / 3), 3)

plot_planes_3d(pc_top, triangles_top, planes_top, ax2, color=COLOR_PALETTE[1])
plot_planes_3d(pc_bottom, triangles_bot, planes_bot,
               ax2, color=COLOR_PALETTE[2])
plt.show(block=False)
print("Showing newly extracted top and bottom planes in Rotated Frame.")
input("Press Enter to Continue: ")
print("")

print("Putting the extracted planes all together back in the Base Frame.")
print("Also plotting the polygon outline of these planes")
# just need to rotate the point cloud back to Base Frame
pc_top = apply_rotation(rm, pc_top)
pc_bottom = apply_rotation(rm, pc_bottom)
plot_planes_3d(pc_top, triangles_top, planes_top, ax, color=COLOR_PALETTE[1])
plot_planes_3d(pc_bottom, triangles_bot, planes_bot,
               ax, color=COLOR_PALETTE[2])
plot_polygons_3d(pc_top, polygons_top, ax, color=COLOR_PALETTE[1])
plot_polygons_3d(pc_bottom, polygons_bot, ax, color=COLOR_PALETTE[2])
ax.legend()
fig.show()
input("Press Enter to Exit: ")


import unittest
import yota
from yota.validators import *
from yota.nodes import *
from yota.exceptions import *

class TestForms(unittest.TestCase):
    def test_override_start_close(self):
        class TForm(yota.Form):
            start = EntryNode()
            close = EntryNode()

            t = EntryNode()
            _t_valid = yota.Check(
                MinLengthValidator(5, message="Darn"), 't')

        test = TForm()
        assert(isinstance(test.start, EntryNode))
        assert(isinstance(test.close, EntryNode))

    def test_override_start_close_exc(self):
        # Test the start node
        class TForm(yota.Form):
            start = ''

        self.assertRaises(AttributeError,
                          TForm)

        # and the close node
        class TForm(yota.Form):
            close = ''

        self.assertRaises(AttributeError,
                          TForm)

    def test_dynamic_insert(self):
        class TForm(yota.Form):
            t = EntryNode()
            _t_valid = yota.Check(
                MinLengthValidator(5, message="Darn"), 't')

        test = TForm()
        test.insert_after('t', EntryNode(_attr_name='t2'))
        assert(test._node_list[2]._attr_name == 't2')
        test.insert_after('t4', EntryNode(_attr_name='t3'))
        assert(test._node_list[4]._attr_name == 't3')

    def test_validator_shorthand(self):
        class TForm(yota.Form):
            t = EntryNode(validators=MinLengthValidator(5,
                          message="Darn"))

        test = TForm()
        assert(len(test._validation_list) > 0)
        assert(isinstance(test._validation_list[0].validator,
                          MinLengthValidator))

    def test_error_header(self):
        class TForm(yota.Form):
            t = EntryNode()
            _t_valid = yota.Check(RequiredValidator(message="Darn"), 't')

            def error_header_generate(self, errors, block):
                self.start.add_error({'message': 'testo'})

        test = TForm()
        test.validate_render({'t': ''})
        assert(hasattr(test.start, 'errors'))

    def test_valid_render_success(self):
        class TForm(yota.Form):
            t = EntryNode()
            _t_valid = yota.Check(RequiredValidator(message="Darn"), 't')

        test = TForm()
        assert(test.validate_render({'t': 'sdflkm'}) is True)

    def test_json_validation(self):
        class TForm(yota.Form):
            t = EntryNode()
            _t_valid = yota.Check(RequiredValidator(message="Darn"), 't')

        test = TForm()
        response = test.json_validate({'t': ''}, piecewise=True)
        assert('Darn' in response)

    def test_piecewise_block(self):
        class TForm(yota.Form):
            t = EntryNode()
            _t_valid = yota.Check(RequiredValidator(message="Darn"), 't')
            b = EntryNode()
            _b_req = yota.Check(MinLengthValidator(5), 'b')

        test = TForm()
        block, invalid = test._gen_validate({'t': 'toolong'}, piecewise=True)
        assert(block is True)

    def test_bad_validator(self):
        class TForm(yota.Form):
            t = EntryNode()
            _t_valid = yota.Check('fgsdfg', 't')

        test = TForm()
        self.assertRaises(NotCallableException,
                          test._gen_validate, {'t': 'toolong'})

    def test_validate_reg(self):
        class TForm(yota.Form):
            t = EntryNode()
            _t_valid = yota.Check(
                MinLengthValidator(5, message="Darn"), 't')

        test = TForm()
        ret = test.validate({'t': 'adfasdfasdf'})
        assert(ret is True)
        invalid = test.validate({'t': ''})
        assert(len(invalid) > 0)

    def test_auto_start_close(self):
        test = yota.Form(auto_start_close=False)
        assert(hasattr(test, 'start') is False)
        assert(hasattr(test, 'close') is False)

class TestExtra(unittest.TestCase):
    def test_get_by_attr(self):
        class TForm(yota.Form):
            t = EntryNode()
            _t_valid = yota.Check(
                MinLengthValidator(5, message="Darn"), 't')

        test = TForm()
        assert(test.get_by_attr('t') is not None)
        assert(test.get_by_attr('g_context') is None)
        assert(test.get_by_attr('dsfglkn') is None)


import os
from conans import ConanFile, tools
from conans.errors import ConanInvalidConfiguration

class NodejsInstallerConan(ConanFile):
    name = "nodejs"
    description = "nodejs binaries for use in recipes"
    topics = ("conan", "node", "nodejs")
    url = "https://github.com/conan-io/conan-center-index"
    homepage = "https://nodejs.org"
    license = "MIT"
    settings = {"os_build": ["Windows", "Linux", "Macos"], "arch_build": ["x86_64"]}
    no_copy_source = True

    @property
    def _source_subfolder(self):
        return os.path.join(self.source_folder, "source_subfolder")

    def source(self):
        os_name = {"Windows": "-win", "Macos": "-darwin", "Linux": "-linux"}
        for data in self.conan_data["sources"][self.version]:
            sha, url = data.values()
            filename = url[url.rfind("/")+1:]
            tools.download(url, filename)
            tools.check_sha256(filename, sha)
            if os_name[str(self.settings.os_build)] in url:
                tools.unzip(filename)
                os.rename(filename[:filename.rfind("x64")+3], self._source_subfolder)

    def package(self):
        self.copy(pattern="LICENSE", dst="licenses", src=self._source_subfolder)
        self.copy(pattern="*", src=os.path.join(self._source_subfolder, "bin"), dst="bin")
        self.copy(pattern="node.exe", src=self._source_subfolder, dst="bin")
        self.copy(pattern="npm", src=self._source_subfolder, dst="bin")
        self.copy(pattern="npx", src=self._source_subfolder, dst="bin")

    def package_info(self):
        bin_dir = os.path.join(self.package_folder, "bin")
        self.output.info('Appending PATH environment variable: {}'.format(bin_dir))
        self.env_info.PATH.append(bin_dir)


#pylint: disable=C0103, C0301, E1101
"""
The utility functions related to images
"""
__author__ = "Noupin, KeeKee"

#Third Party Imports
import io
import os
import cv2
import base64
import piexif
import numpy as np
from PIL import Image
from colorama import Fore
import matplotlib.pyplot as plt
from moviepy import editor as mediaEditor
from typing import Any, Dict, List, Tuple, Generator, Union

#First Party Imports
from ..exceptions.CVToPIL import CVToPILError
from ..exceptions.IncompatibleShapes import IncompatibleShapeError

def PILToCV(image: Image.Image) -> np.ndarray:
    """
    Given a PIL image it will be converted to a CV image

    Args:
        image (PIL.Image.Image): The PIL Image to be converted to CV

    Returns:
        numpy.ndarray: The CV image
    """

    converted = np.asarray(image)

    return converted

def CVToPIL(image: np.ndarray) -> Image.Image:
    """
    Given a CV image it will be converted to a PIL image

    Args:
        image (numpy.ndarray): The CV image to be converted to PIL

    Returns:
        PIL.Image.Image: The PIL image
    """

    if isinstance(image, Image.Image):
        try:
            raise CVToPILError(image)
        except CVToPILError as error:
            print(Fore.RED + error + Fore.RESET)
            print(Fore.GREEN + "Conversion skipped." + Fore.RESET)
            return image

    converted = Image.fromarray(image)

    return converted

def loadImage(path: str) -> np.ndarray:
    """
    Given a path the image is loaded and converted to the RGB color space

    Args:
        path (str): The path to the image to be loaded

    Returns:
        numpy.ndarray: The RGB CV image
    """

    image = cv2.imread(path)
    image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR)

    return image

def loadImageFolder(path: str, maximum=-1) -> Generator[np.ndarray, None, None]:
    """
    Loads in a folder of images.

    Args:
        path (str): The path to the folder with the images.
        maximum (int): The maximum number of images to grab from the folder. Defaults to -1.

    Yields:
        Generator of np.ndarray: The images from the folder path given
    """

    for index, imageName in enumerate(os.listdir(path)):
        if index == maximum:
            break

        image = loadImage(os.path.join(path, imageName))

        yield image

def saveImage(image: Image.Image, path: str) -> None:
    """
    Saves the image to the given path

    Args:
        image (PIL.Image.Image): The image to be saved
        path (str): The path to where the file is saved
    """

    _, extenstion = os.path.splitext(path)
    if extenstion.find('.') != -1:
        image.save(path)
    else:
        image.save(f"{path}.png")
        os.rename(f"{path}.png", path)

def resizeImage(image: np.ndarray, size: Tuple[int], keepAR=False, maxDim: int=None,
                interpolation=cv2.INTER_AREA) -> np.ndarray:
    """
    Given a cv2 image it is resized to the given size

    Args:
        image (numpy.ndarray): The image object to be resized
        size (tuple of int): A tuple of the desired x(width) and y(height) dimension
        keepAR (bool, optional): Whether or not to keep the aspect ratio of the
                       image. Defaults to False.
        maxDim (int, optional): The maximum size of either dimension to resize. Defaults to None.
        interpolation (int, optional): The type of image interpolation to downscale the image.
                                       Defaults to cv2.INTER_AREA.

    Returns:
        numpy.ndarray: The resized CV image
    """

    resized = PILToCV(image)

    if keepAR:
        height, width = image.shape[:2]

        if not maxDim and size[0] is None:
            ratio = size[1] / float(height)
            dimensions = (int(width * ratio), size[1])
        elif not maxDim and size[1] is None:
            ratio = size[0] / float(width)
            dimensions = (size[0], int(height * ratio))
        elif maxDim and height >= width and height > maxDim:
            ratio = maxDim / float(height)
            dimensions = (int(width * ratio), maxDim)
        elif maxDim and width >= height and width > maxDim:
            ratio = maxDim / float(width)
            dimensions = (maxDim, int(height * ratio))
        else:
            dimensions = (width, height)

        resized = cv2.resize(resized, dimensions, interpolation=interpolation)
    else:
        resized = cv2.resize(resized, size, interpolation=interpolation)

    return resized

def cropImage(image: np.ndarray, cropArea: Tuple[int]) -> np.ndarray:
    """
    Crops the crop area out of image

    Args:
        image (numpy.ndarray): The image to be cropped
        cropArea (tuple of int): The x, y, width and height values of
                                 the rectange to be cropped from image

    Returns:
        numpy.ndarray: The cropped portion of the image
    """

    x, y, width, height = cropArea
    cropped = image[int(y):int(y+height), int(x):int(x+width)]

    return cropped

def replaceAreaOfImage(fullImage: np.ndarray, replaceArea: Tuple[int], replaceImage: np.ndarray) -> np.ndarray:
    """
    Replaces a certain area of an image with another image. Assuming that
    the size of the replace image fits the dimensions of replaceArea.

    Args:
        fullImage (np.ndarray): The image to have an area replaced within
        replaceArea (Tuple[int]): The x, y, width and height values of the
                                  area to replace fullImage with replaceImage
        replaceImage (np.ndarray): The image to replace the replaceArea of fullImage

    Returns:
        np.ndarray: The image with replace area replaced by replaceImage
    """

    x, y, width, height = replaceArea
    fullImage[int(y):int(y+height), int(x):int(x+width)] = replaceImage

    return fullImage

def viewImage(image, **kwargs) -> None:
    """
    Given a CV image or a PIL image it will be previewed using matplotlib

    Args:
        image (PIL.Image.Image or numpy.ndarray): The image to be displayed
        **kwargs: Arguments to be applied to matplotlib.pyplot.imshow
    """

    plt.imshow(image, **kwargs)
    plt.show()

def encodeImage(image: Image.Image) -> str:
    """
    Takes in an image array then encodes it as a bytestring to be streamed through JSON to JavaScript.

    Args:
        image (numpy.ndarray): The image to be converted to binary string

    Returns:
        str: The binary encoding of the image
    """

    buffered = io.BytesIO()
    image.save(buffered, format="PNG")
    imageString = base64.b64encode(buffered.getvalue())
    encodedImage = imageString.decode('utf-8')

    return encodedImage

def decodeImage(encodedImage: str) -> np.ndarray:
    """
    Given an encoded image as a binary string it will be decoded into a CV image

    Args:
        encodedImage (str): The encoded image binary string

    Returns:
        numpy.ndarray: The decoded image
    """

    decodedImage = base64.b64decode(encodedImage)
    image = Image.open(io.BytesIO(decodedImage))

    return image

def blendImageAndColor(image, colorCode: int) -> np.ndarray:
    """
    Given an input image an augmented image is returned.

    Args:
        image (PIL.Image.Image or numpy.ndarray): The image to be masked by a given color
        colorCode (int): The corresponding integer to the colorMasks values

    Returns:
        numpy.ndarray: The color masked image
    """

    colorMasks = {0: (255, 0, 0), #Red
                  1: (0, 255, 0), #Green
                  2: (0, 0, 255), #Blue
                  3: (0, 0, 0), #Black
                  4: (255, 255, 255)} # White
    alphaLevels = {0: 0.225, #Red alpha level
                   1: 0.225, #Green alpha level
                   2: 0.2, #Blue alpha level
                   3: 0.4, #Black alpha level
                   4: 0.4} #White alpha level

    image = CVToPIL(image)
    colorImage = Image.new("RGB", image.size,
                           colorMasks[colorCode])
    augmenetedImage = Image.blend(image, colorImage, alphaLevels[colorCode])

    image = PILToCV(augmenetedImage)

    return image

def flipImage(image, flipCode: str) -> np.ndarray:
    """
    The image will be flipped by the corresponding flip code.

    Args:
        image (PIL.Image.Image or numpy.ndarray): [description]
        flipCode (str): The corresponding string to the flip values

    Returns:
        numpy.ndarray: The flipped image
    """

    flipMap = {"x": 0,
               "y": 1,
               "xy": -1,
               "yx": -1}
    image = PILToCV(image)

    return cv2.flip(image, flipMap[flipCode])

def imagesToVideo(images: Generator[np.ndarray, None, None], shape: Tuple[int], outPath: str, fps: float) -> mediaEditor.VideoFileClip:
    """
    Given a path with all of the image arrays a .mp4 video will be exported

    Args:
        images (generator of np.ndarray): An array of CV images
        shape (tuple of int): The height, width and color dimension of the frames to converted to a video.
        outPath (str): The path to save the video to
        fps (float): The fps at which to combine the video at
    """

    height, width, _ = shape

    codec = cv2.VideoWriter_fourcc(*'H264') #H264, X264, MP42, MP4A
    videoWrite = cv2.VideoWriter(outPath,
                                 codec,
                                 fps,
                                 (width, height))

    for image in images:
        if isinstance(image, np.ndarray) and image.shape != shape:
            raise IncompatibleShapeError(image.shape, shape)

        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
        videoWrite.write(image)

    videoWrite.release()
    cv2.destroyAllWindows()

    video = mediaEditor.VideoFileClip(outPath)

    return video

def createMask(image: np.ndarray, **kwargs) -> np.ndarray:
    """
    Given an image a black and white mask will be generated

    Args:
        image (np.ndarray): [description]
        kwargs: [desc]

    Returns:
        np.ndarray: [description]
    """

    try:
        kwargs["gray"]
    except KeyError:
        kwargs["gray"] = False

    if kwargs["gray"]:
        del kwargs["gray"]
        grayImage = cv2.cvtColor(image, cv2.COLOR_RGB2GRAY)
    else:
        grayImage = image.copy()

    _, thresh = cv2.threshold(grayImage, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

    kernel = np.ones((3, 3), np.uint8)
    closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)

    distTransform = cv2.distanceTransform(closing, cv2.DIST_L2, 0)
    _, foreground = cv2.threshold(distTransform, 0.2 * distTransform.max(), 255, 0)

    foreground = foreground.astype(np.uint8)
    background = cv2.bitwise_not(foreground)

    mask = cv2.bitwise_and(image, image, mask=foreground)

    return mask

def maskImage(baseImage: np.ndarray, maskImage: np.ndarray, **kwargs) -> np.ndarray:
    """
    Given a base image a mask area will be generated and the portion of the mask image will be

    Args:
        baseImage (np.ndarray): [description]
        maskImage (np.ndarray): [description]

    Returns:
        np.ndarray: [description]
    """

    try:
        kwargs["gray"]
    except KeyError:
        kwargs["gray"] = False

    if kwargs["gray"]:
        del kwargs["gray"]
        grayBaseImage = cv2.cvtColor(baseImage, cv2.COLOR_RGB2GRAY)

    _, thresh = cv2.threshold(grayBaseImage, 0, 255, cv2.THRESH_BINARY_INV + cv2.THRESH_OTSU)

    kernel = np.ones((3, 3), np.uint8)
    closing = cv2.morphologyEx(thresh, cv2.MORPH_CLOSE, kernel, iterations=2)

    distTransform = cv2.distanceTransform(closing, cv2.DIST_L2, 0)
    _, foreground = cv2.threshold(distTransform, 0.2 * distTransform.max(), 255, 0)

    foreground = foreground.astype(np.uint8)
    background = cv2.bitwise_not(foreground)

    baseImage = cv2.bitwise_and(baseImage, baseImage, mask=foreground)
    maskImage = cv2.bitwise_and(maskImage, maskImage, mask=background)

    maskedImage = cv2.add(maskImage, baseImage)

    return maskedImage

def drawPolygon(image: np.ndarray, points: List[List[int]], color=(255, 255, 255), mask=False) -> np.ndarray:
    """
    Connects the dots and fills the newly made polygon with color. If needed
    the background can be set to black and a masking image can be returned.

    Args:
        image (np.ndarray): The image to draw the polygon ontop of.
        points (List[List[int]]): The points to connect together.
        color (tuple, optional): The color to fill the polygon. Defaults to (255, 255, 255).
        mask (bool, optional): Whether the image should be converted to a mask. Defaults to False.

    Returns:
        np.ndarray: The polygon drawn ontop of image or the drawn mask.
    """

    polyImage = image.copy()
    if mask:
        polyImage = np.zeros(image.shape[0:2], dtype=np.uint8)

    cv2.fillPoly(polyImage, pts=[points], color=color)

    return polyImage

def applyMask(baseImage: np.ndarray, maskImage: np.ndarray, mask: np.ndarray, dtype=cv2.CV_8U) -> np.ndarray:
    """
    Applies mask to baseImage and overlays maskImage in the correspodning area.

    Args:
        baseImage (np.ndarray): The image to have the mask applied to.
        maskImage (np.ndarray): The image to apply as a mask to the base image.
        mask (np.ndarray): The black and white bitwise mask to be applied.
        dtype (int, optional): The datatype of baseImage and maskImage. Defaults to cv2.CV_8U.

    Returns:
        np.ndarray: An output of maskImage overlayed on baseImage in using the area from mask
    """

    inverseMask = cv2.bitwise_not(mask)

    base = cv2.bitwise_and(baseImage, baseImage, mask=inverseMask)
    mask = cv2.bitwise_and(maskImage, maskImage, mask=mask)

    maskedImage = cv2.add(base, mask, dtype=dtype)

    return maskedImage

def compressImage(image: Image.Image, quality=65) -> Image.Image:
    """
    Compresses the fiven image to preserve fidelity and decrease file size.

    Args:
        image (PIL.Image.Image): The image to be compressed
        quality (int, optional): The quality of the compression from 0-100. Defaults to 65.

    Returns:
        Image.Image: The compressed image.
    """

    compressed = image.convert("RGB")

    buffered = io.BytesIO()
    compressed.save(buffered, format="JPEG", optimize=True, quality=quality)
    buffered.seek(0)

    return Image.open(buffered)

def imageFilesize(image: Image.Image) -> int:
    """
    Gets the filesize of the image in bytes.

    Args:
        image (PIL.Image.Image): The image to be measured.

    Returns:
        int: The size of the image in bytes.
    """

    buffered = io.BytesIO()
    image.save(buffered, format="PNG")

    return buffered.tell()

def editImageMetadata(image: Image.Image, key: Union[str, int],
                      value: Dict[str, Any]) -> Image.Image:
    """
    Edits the metadata of a PIL image.

    Args:
        image (PIL.Image.Image): The image to edit the metadata of.
        key (Union[str, int]): The key to add to the metadata.
        value (Union[str, int]): The value to associate with the key in the metadata.

    Returns:
        PIL.Image.Image: The edited metadata.
    """

    exifDict = image.getexif()

    if exifDict is None:
        exifDict = {}

    exifDict[key] = value
    exifBytes = piexif.dump(exifDict)

    buffered = io.BytesIO()
    image.save(buffered, format="PNG", exif=exifBytes)
    buffered.seek(0)

    return Image.open(buffered)


from neurolight.tensorflow.add_loss import create_custom_loss
from neurolight.gunpowder.nodes import (
    TopologicalMatcher,
    RasterizeSkeleton,
    GetNeuronPair,
    FusionAugment,
    GrowLabels,
)
import gunpowder as gp
import numpy as np

import math
from pathlib import Path
import copy
import logging

logger = logging.getLogger(__file__)

class BinarizeGt(gp.BatchFilter):
    def __init__(self, gt, gt_binary):

        self.gt = gt
        self.gt_binary = gt_binary

    def setup(self):

        spec = self.spec[self.gt].copy()
        spec.dtype = np.uint8
        self.provides(self.gt_binary, spec)

    def prepare(self, request):
        pass

    def process(self, batch, request):

        spec = batch[self.gt].spec.copy()
        spec.dtype = np.int32

        binarized = gp.Array(data=(batch[self.gt].data > 0).astype(np.int32), spec=spec)

        batch[self.gt_binary] = binarized

class Crop(gp.BatchFilter):
    def __init__(self, input_array: gp.ArrayKey, output_array: gp.ArrayKey):

        self.input_array = input_array
        self.output_array = output_array

    def setup(self):

        spec = self.spec[self.input_array].copy()
        self.provides(self.output_array, spec)

    def prepare(self, request):
        pass

    def process(self, batch, request):
        input_data = batch[self.input_array].data
        input_spec = batch[self.input_array].spec
        input_roi = input_spec.roi
        output_roi_shape = request[self.output_array].roi.get_shape()
        shift = (input_roi.get_shape() - output_roi_shape) / 2
        output_roi = gp.Roi(shift, output_roi_shape)
        output_data = input_data[
            tuple(
                map(
                    slice,
                    output_roi.get_begin() / input_spec.voxel_size,
                    output_roi.get_end() / input_spec.voxel_size,
                )
            )
        ]
        output_spec = copy.deepcopy(input_spec)
        output_spec.roi = output_roi

        output_array = gp.Array(output_data, output_spec)

        batch[self.output_array] = output_array

class RejectIfEmpty(gp.BatchFilter):
    def __init__(self, ensure_nonempty: gp.ArrayKey, request_limit: int = 100):
        self.ensure_nonempty = ensure_nonempty
        self.request_limit = request_limit

    def setup(self):
        self.enable_autoskip()
        self.updates(
            self.ensure_nonempty, copy.deepcopy(self.spec[self.ensure_nonempty])
        )

    def prepare(self, request):
        return copy.deepcopy(request)

    def process(self, batch, request):

        k = 0
        found_valid = False
        while (
            not found_valid
            and k < self.request_limit
            and self.ensure_nonempty in request
        ):
            batch = self.get_upstream_provider().request_batch(request)
            if len(batch[self.ensure_nonempty].graph.nodes) > 0:
                found_valid = True
            k += 1

        return batch

def train_fusion_pipeline(
    n_iterations,
    setup_config,
    emb_mknet_tensor_names,
    emb_loss_tensor_names,
    fg_mknet_tensor_names,
):
    input_shape = gp.Coordinate(setup_config["INPUT_SHAPE"])
    output_shape = gp.Coordinate(setup_config["OUTPUT_SHAPE"])
    voxel_size = gp.Coordinate(setup_config["VOXEL_SIZE"])
    shift_attempts = setup_config["SHIFT_ATTEMPTS"]
    request_attempts = setup_config["REQUEST_ATTEMPTS"]
    num_iterations = setup_config["NUM_ITERATIONS"]
    cache_size = setup_config["CACHE_SIZE"]
    num_workers = setup_config["NUM_WORKERS"]
    snapshot_every = setup_config["SNAPSHOT_EVERY"]
    checkpoint_every = setup_config["CHECKPOINT_EVERY"]
    profile_every = setup_config["PROFILE_EVERY"]
    seperate_by = setup_config["SEPERATE_BY"]
    gap_crossing_dist = setup_config["GAP_CROSSING_DIST"]
    match_distance_threshold = setup_config["MATCH_DISTANCE_THRESHOLD"]
    point_balance_radius = setup_config["POINT_BALANCE_RADIUS"]
    neuron_radius = setup_config["NEURON_RADIUS"]
    blend_smoothness = setup_config["BLEND_SMOOTHNESS"]
    foreground_net_checkpoint = setup_config["FOREGROUND_NET_CHECKPOINT"]
    raw_n5 = setup_config["RAW_N5"]

    samples_path = Path(setup_config["SAMPLES_PATH"])
    mongo_url = setup_config["MONGO_URL"]

    input_size = input_shape * voxel_size
    output_size = output_shape * voxel_size
    # voxels have size ~= 1 micron on z axis
    # use this value to scale anything that depends on world unit distance
    micron_scale = voxel_size[0]
    seperate_distance = (np.array(seperate_by)).tolist()

    # array keys for data sources
    raw = gp.ArrayKey("RAW")
    consensus = gp.PointsKey("CONSENSUS")
    skeletonization = gp.PointsKey("SKELETONIZATION")
    matched = gp.PointsKey("MATCHED")
    nonempty_placeholder = gp.PointsKey("NONEMPTY")
    labels = gp.ArrayKey("LABELS")

    # array keys for base volume
    raw_base = gp.ArrayKey("RAW_BASE")
    labels_base = gp.ArrayKey("LABELS_BASE")
    matched_base = gp.PointsKey("MATCHED_BASE")
    skeletonization_base = gp.PointsKey("SKELETONIZATION_BASE")
    consensus_base = gp.PointsKey("CONSENSUS_BASE")

    # array keys for add volume
    raw_add = gp.ArrayKey("RAW_ADD")
    labels_add = gp.ArrayKey("LABELS_ADD")
    matched_add = gp.PointsKey("MATCHED_ADD")
    skeletonization_add = gp.PointsKey("SKELETONIZATION_ADD")
    consensus_add = gp.PointsKey("CONSENSUS_ADD")

    # array keys for fused volume
    raw_fused = gp.ArrayKey("RAW_FUSED")
    labels_fused = gp.ArrayKey("LABELS_FUSED")
    matched_fused = gp.PointsKey("MATCHED_FUSED")
    labels_fg = gp.ArrayKey("LABELS_FG")
    labels_fg_bin = gp.ArrayKey("LABELS_FG_BIN")
    loss_weights = gp.ArrayKey("LOSS_WEIGHTS")

    # debug array keys
    soft_mask = gp.ArrayKey("SOFT_MASK")
    masked_base = gp.ArrayKey("MASKED_BASE")
    masked_add = gp.ArrayKey("MASKED_ADD")
    mask_maxed = gp.ArrayKey("MASK_MAXED")

    # tensorflow tensors
    gt_fg = gp.ArrayKey("GT_FG")
    fg_pred = gp.ArrayKey("FG_PRED")
    embedding = gp.ArrayKey("EMBEDDING")
    fg = gp.ArrayKey("FG")
    maxima = gp.ArrayKey("MAXIMA")
    gradient_embedding = gp.ArrayKey("GRADIENT_EMBEDDING")
    gradient_fg = gp.ArrayKey("GRADIENT_FG")
    emst = gp.ArrayKey("EMST")
    edges_u = gp.ArrayKey("EDGES_U")
    edges_v = gp.ArrayKey("EDGES_V")
    ratio_pos = gp.ArrayKey("RATIO_POS")
    ratio_neg = gp.ArrayKey("RATIO_NEG")
    dist = gp.ArrayKey("DIST")
    num_pos_pairs = gp.ArrayKey("NUM_POS")
    num_neg_pairs = gp.ArrayKey("NUM_NEG")

    # add request
    request = gp.BatchRequest()
    request.add(raw_fused, input_size)
    request.add(labels_fused, input_size)
    request.add(matched_fused, input_size)
    request.add(labels_fg, output_size)
    request.add(gt_fg, output_size)
    request.add(loss_weights, output_size)

    # add snapshot request
    snapshot_request = gp.BatchRequest()
    request.add(labels_fg, output_size)
    request.add(raw_base, input_size)
    request.add(raw_add, input_size)
    request.add(labels_base, input_size)
    request.add(labels_add, input_size)
    request.add(matched_base, input_size)
    request.add(matched_add, input_size)
    request.add(skeletonization_base, input_size)
    request.add(skeletonization_add, input_size)
    request.add(consensus_base, input_size)
    request.add(consensus_add, input_size)

    # add debug requests
    snapshot_request.add(soft_mask, input_size, voxel_size=voxel_size)
    snapshot_request.add(masked_base, input_size, voxel_size=voxel_size)
    snapshot_request.add(masked_add, input_size, voxel_size=voxel_size)
    snapshot_request.add(mask_maxed, input_size, voxel_size=voxel_size)

    # tensorflow requests
    snapshot_request.add(raw_fused, input_size)  # input_size request for positioning
    snapshot_request.add(embedding, output_size, voxel_size=voxel_size)
    snapshot_request.add(fg, output_size, voxel_size=voxel_size)
    snapshot_request.add(gt_fg, output_size, voxel_size=voxel_size)
    snapshot_request.add(fg_pred, output_size, voxel_size=voxel_size)
    snapshot_request.add(maxima, output_size, voxel_size=voxel_size)
    snapshot_request.add(gradient_embedding, output_size, voxel_size=voxel_size)
    snapshot_request.add(gradient_fg, output_size, voxel_size=voxel_size)
    snapshot_request[emst] = gp.ArraySpec()
    snapshot_request[edges_u] = gp.ArraySpec()
    snapshot_request[edges_v] = gp.ArraySpec()
    snapshot_request[ratio_pos] = gp.ArraySpec()
    snapshot_request[ratio_neg] = gp.ArraySpec()
    snapshot_request[dist] = gp.ArraySpec()
    snapshot_request[num_pos_pairs] = gp.ArraySpec()
    snapshot_request[num_neg_pairs] = gp.ArraySpec()

    data_sources = tuple(
        (
            gp.N5Source(
                filename=str((sample / raw_n5).absolute()),
                datasets={raw: "volume"},
                array_specs={
                    raw: gp.ArraySpec(
                        interpolatable=True, voxel_size=voxel_size, dtype=np.uint16
                    )
                },
            ),
            gp.DaisyGraphProvider(
                f"mouselight-{sample.name}-consensus",
                mongo_url,
                points=[consensus, nonempty_placeholder],
                directed=True,
                node_attrs=[],
                edge_attrs=[],
            ),
            gp.DaisyGraphProvider(
                f"mouselight-{sample.name}-skeletonization",
                mongo_url,
                points=[skeletonization],
                directed=False,
                node_attrs=[],
                edge_attrs=[],
            ),
        )
        + gp.MergeProvider()
        + gp.RandomLocation(
            ensure_nonempty=nonempty_placeholder,
            ensure_centered=True,
            point_balance_radius=point_balance_radius * micron_scale,
        )
        + TopologicalMatcher(
            skeletonization,
            consensus,
            matched,
            failures=Path("matching_failures_slow"),
            match_distance_threshold=match_distance_threshold * micron_scale,
            max_gap_crossing=gap_crossing_dist * micron_scale,
            try_complete=False,
            use_gurobi=True,
        )
        + RasterizeSkeleton(
            points=matched,
            array=labels,
            array_spec=gp.ArraySpec(
                interpolatable=False, voxel_size=voxel_size, dtype=np.uint32
            ),
        )
        + GrowLabels(labels, radii=[neuron_radius * micron_scale])
        # TODO: Do these need to be scaled by world units?
        + gp.ElasticAugment(
            [40, 10, 10],
            [0.25, 1, 1],
            [0, math.pi / 2.0],
            subsample=4,
            use_fast_points_transform=True,
            recompute_missing_points=False,
        )
        # + gp.SimpleAugment(mirror_only=[1, 2], transpose_only=[1, 2])
        + gp.Normalize(raw) + gp.IntensityAugment(raw, 0.9, 1.1, -0.001, 0.001)
        for sample in samples_path.iterdir()
        if sample.name in ("2018-07-02", "2018-08-01")
    )

    pipeline = (
        data_sources
        + gp.RandomProvider()
        + GetNeuronPair(
            matched,
            raw,
            labels,
            (matched_base, matched_add),
            (raw_base, raw_add),
            (labels_base, labels_add),
            output_shape=output_size,
            seperate_by=seperate_distance,
            shift_attempts=shift_attempts,
            request_attempts=request_attempts,
            nonempty_placeholder=nonempty_placeholder,
            extra_keys={
                skeletonization: (skeletonization_base, skeletonization_add),
                consensus: (consensus_base, consensus_add),
            },
        )
        + FusionAugment(
            raw_base,
            raw_add,
            labels_base,
            labels_add,
            matched_base,
            matched_add,
            raw_fused,
            labels_fused,
            matched_fused,
            soft_mask=soft_mask,
            masked_base=masked_base,
            masked_add=masked_add,
            mask_maxed=mask_maxed,
            blend_mode="labels_mask",
            blend_smoothness=blend_smoothness * micron_scale,
            gaussian_smooth_mode="mirror",
            num_blended_objects=0,
        )
        + Crop(labels_fused, labels_fg)
        + BinarizeGt(labels_fg, gt_fg)
        + gp.BalanceLabels(gt_fg, loss_weights)
        + gp.PreCache(cache_size=cache_size, num_workers=num_workers)
        + gp.tensorflow.Predict(
            foreground_net_checkpoint,
            graph=f"{foreground_net_checkpoint}.meta",
            inputs={fg_mknet_tensor_names["raw"]: raw_fused},
            outputs={fg_mknet_tensor_names["fg_pred"]: fg_pred},
        )
        + gp.tensorflow.Train(
            "embedding_net",
            optimizer=create_custom_loss(emb_mknet_tensor_names, setup_config),
            loss=None,
            inputs={
                emb_mknet_tensor_names["loss_weights"]: loss_weights,
                emb_mknet_tensor_names["raw"]: raw_fused,
                emb_mknet_tensor_names["gt_labels"]: labels_fg,
                emb_mknet_tensor_names["fg_pred"]: fg_pred,
            },
            outputs={
                emb_mknet_tensor_names["embedding"]: embedding,
                emb_loss_tensor_names["maxima"]: maxima,
                emb_loss_tensor_names["emst"]: emst,
                emb_loss_tensor_names["edges_u"]: edges_u,
                emb_loss_tensor_names["edges_v"]: edges_v,
                emb_loss_tensor_names["ratio_pos"]: ratio_pos,
                emb_loss_tensor_names["ratio_neg"]: ratio_neg,
                emb_loss_tensor_names["dist"]: dist,
                emb_loss_tensor_names["num_pos_pairs"]: num_pos_pairs,
                emb_loss_tensor_names["num_neg_pairs"]: num_neg_pairs,
            },
            gradients={
                emb_mknet_tensor_names["embedding"]: gradient_embedding,
            },
            save_every=checkpoint_every,
            summary="Merge/MergeSummary:0",
            log_dir="tensorflow_logs",
        )
        + gp.PrintProfilingStats(every=profile_every)
        + gp.Snapshot(
            additional_request=snapshot_request,
            output_filename="snapshot_{}_{}.hdf".format(
                int(np.min(seperate_distance)), "{id}"
            ),
            dataset_names={
                # raw data
                raw_fused: "volumes/raw_fused",
                raw_base: "volumes/raw_base",
                raw_add: "volumes/raw_add",
                # labeled data
                labels_fused: "volumes/labels_fused",
                labels_base: "volumes/labels_base",
                labels_add: "volumes/labels_add",
                # trees
                skeletonization_base: "points/skeletonization_base",
                skeletonization_add: "points/skeletonization_add",
                consensus_base: "points/consensus_base",
                consensus_add: "points/consensus_add",
                matched_base: "points/matched_base",
                matched_add: "points/matched_add",
                matched_fused: "points/matched_fused",
                # output volumes
                embedding: "volumes/embedding",
                fg: "volumes/fg",
                maxima: "volumes/maxima",
                gt_fg: "volumes/gt_fg",
                fg_pred: "volumes/fg_pred",
                gradient_embedding: "volumes/gradient_embedding",
                # output trees
                emst: "emst",
                edges_u: "edges_u",
                edges_v: "edges_v",
                # output debug data
                ratio_pos: "ratio_pos",
                ratio_neg: "ratio_neg",
                dist: "dist",
                num_pos_pairs: "num_pos_pairs",
                num_neg_pairs: "num_neg_pairs",
                # debug volumes
                soft_mask: "volumes/soft_mask",
                masked_base: "volumes/masked_base",
                masked_add: "volumes/masked_add",
                mask_maxed: "volumes/mask_maxed",
                loss_weights: "volumes/loss_weights",
            },
            every=snapshot_every,
        )
    )

    with gp.build(pipeline):
        for _ in range(num_iterations):
            pipeline.request_batch(request)

def train_simple_pipeline(
    n_iterations, setup_config, mknet_tensor_names, loss_tensor_names
):
    input_shape = gp.Coordinate(setup_config["INPUT_SHAPE"])
    output_shape = gp.Coordinate(setup_config["OUTPUT_SHAPE"])
    voxel_size = gp.Coordinate(setup_config["VOXEL_SIZE"])
    num_iterations = setup_config["NUM_ITERATIONS"]
    cache_size = setup_config["CACHE_SIZE"]
    num_workers = setup_config["NUM_WORKERS"]
    snapshot_every = setup_config["SNAPSHOT_EVERY"]
    checkpoint_every = setup_config["CHECKPOINT_EVERY"]
    profile_every = setup_config["PROFILE_EVERY"]
    seperate_by = setup_config["SEPERATE_BY"]
    gap_crossing_dist = setup_config["GAP_CROSSING_DIST"]
    match_distance_threshold = setup_config["MATCH_DISTANCE_THRESHOLD"]
    point_balance_radius = setup_config["POINT_BALANCE_RADIUS"]
    neuron_radius = setup_config["NEURON_RADIUS"]

    samples_path = Path(setup_config["SAMPLES_PATH"])
    mongo_url = setup_config["MONGO_URL"]

    input_size = input_shape * voxel_size
    output_size = output_shape * voxel_size
    # voxels have size ~= 1 micron on z axis
    # use this value to scale anything that depends on world unit distance
    micron_scale = voxel_size[0]
    seperate_distance = (np.array(seperate_by)).tolist()

    # array keys for data sources
    raw = gp.ArrayKey("RAW")
    consensus = gp.PointsKey("CONSENSUS")
    skeletonization = gp.PointsKey("SKELETONIZATION")
    matched = gp.PointsKey("MATCHED")
    labels = gp.ArrayKey("LABELS")

    labels_fg = gp.ArrayKey("LABELS_FG")
    labels_fg_bin = gp.ArrayKey("LABELS_FG_BIN")
    loss_weights = gp.ArrayKey("LOSS_WEIGHTS")

    # tensorflow tensors
    gt_fg = gp.ArrayKey("GT_FG")
    fg_pred = gp.ArrayKey("FG_PRED")
    embedding = gp.ArrayKey("EMBEDDING")
    fg = gp.ArrayKey("FG")
    maxima = gp.ArrayKey("MAXIMA")
    gradient_embedding = gp.ArrayKey("GRADIENT_EMBEDDING")
    gradient_fg = gp.ArrayKey("GRADIENT_FG")
    emst = gp.ArrayKey("EMST")
    edges_u = gp.ArrayKey("EDGES_U")
    edges_v = gp.ArrayKey("EDGES_V")
    ratio_pos = gp.ArrayKey("RATIO_POS")
    ratio_neg = gp.ArrayKey("RATIO_NEG")
    dist = gp.ArrayKey("DIST")
    num_pos_pairs = gp.ArrayKey("NUM_POS")
    num_neg_pairs = gp.ArrayKey("NUM_NEG")

    # add request
    request = gp.BatchRequest()
    request.add(labels_fg, output_size)
    request.add(labels_fg_bin, output_size)
    request.add(loss_weights, output_size)
    request.add(raw, input_size)
    request.add(labels, input_size)
    request.add(matched, input_size)
    request.add(skeletonization, input_size)
    request.add(consensus, input_size)

    # add snapshot request
    snapshot_request = gp.BatchRequest()
    request.add(labels_fg, output_size)

    # tensorflow requests
    # snapshot_request.add(raw, input_size)  # input_size request for positioning
    # snapshot_request.add(embedding, output_size, voxel_size=voxel_size)
    # snapshot_request.add(fg, output_size, voxel_size=voxel_size)
    # snapshot_request.add(gt_fg, output_size, voxel_size=voxel_size)
    # snapshot_request.add(fg_pred, output_size, voxel_size=voxel_size)
    # snapshot_request.add(maxima, output_size, voxel_size=voxel_size)
    # snapshot_request.add(gradient_embedding, output_size, voxel_size=voxel_size)
    # snapshot_request.add(gradient_fg, output_size, voxel_size=voxel_size)
    # snapshot_request[emst] = gp.ArraySpec()
    # snapshot_request[edges_u] = gp.ArraySpec()
    # snapshot_request[edges_v] = gp.ArraySpec()
    # snapshot_request[ratio_pos] = gp.ArraySpec()
    # snapshot_request[ratio_neg] = gp.ArraySpec()
    # snapshot_request[dist] = gp.ArraySpec()
    # snapshot_request[num_pos_pairs] = gp.ArraySpec()
    # snapshot_request[num_neg_pairs] = gp.ArraySpec()

    data_sources = tuple(
        (
            gp.N5Source(
                filename=str((sample / "fluorescence-near-consensus.n5").absolute()),
                datasets={raw: "volume"},
                array_specs={
                    raw: gp.ArraySpec(
                        interpolatable=True, voxel_size=voxel_size, dtype=np.uint16
                    )
                },
            ),
            gp.DaisyGraphProvider(
                f"mouselight-{sample.name}-consensus",
                mongo_url,
                points=[consensus],
                directed=True,
                node_attrs=[],
                edge_attrs=[],
            ),
            gp.DaisyGraphProvider(
                f"mouselight-{sample.name}-skeletonization",
                mongo_url,
                points=[skeletonization],
                directed=False,
                node_attrs=[],
                edge_attrs=[],
            ),
        )
        + gp.MergeProvider()
        + gp.RandomLocation(
            ensure_nonempty=consensus,
            ensure_centered=True,
            point_balance_radius=point_balance_radius * micron_scale,
        )
        + TopologicalMatcher(
            skeletonization,
            consensus,
            matched,
            failures=Path("matching_failures_slow"),
            match_distance_threshold=match_distance_threshold * micron_scale,
            max_gap_crossing=gap_crossing_dist * micron_scale,
            try_complete=False,
            use_gurobi=True,
        )
        + RejectIfEmpty(matched)
        + RasterizeSkeleton(
            points=matched,
            array=labels,
            array_spec=gp.ArraySpec(
                interpolatable=False, voxel_size=voxel_size, dtype=np.uint32
            ),
        )
        + GrowLabels(labels, radii=[neuron_radius * micron_scale])
        # TODO: Do these need to be scaled by world units?
        + gp.ElasticAugment(
            [40, 10, 10],
            [0.25, 1, 1],
            [0, math.pi / 2.0],
            subsample=4,
            use_fast_points_transform=True,
            recompute_missing_points=False,
        )
        # + gp.SimpleAugment(mirror_only=[1, 2], transpose_only=[1, 2])
        + gp.Normalize(raw) + gp.IntensityAugment(raw, 0.9, 1.1, -0.001, 0.001)
        for sample in samples_path.iterdir()
        if sample.name in ("2018-07-02", "2018-08-01")
    )

    pipeline = (
        data_sources
        + gp.RandomProvider()
        + Crop(labels, labels_fg)
        + BinarizeGt(labels_fg, labels_fg_bin)
        + gp.BalanceLabels(labels_fg_bin, loss_weights)
        + gp.PreCache(cache_size=cache_size, num_workers=num_workers)
        + gp.tensorflow.Train(
            "train_net",
            optimizer=create_custom_loss(mknet_tensor_names, setup_config),
            loss=None,
            inputs={
                mknet_tensor_names["loss_weights"]: loss_weights,
                mknet_tensor_names["raw"]: raw,
                mknet_tensor_names["gt_labels"]: labels_fg,
            },
            outputs={
                mknet_tensor_names["embedding"]: embedding,
                mknet_tensor_names["fg"]: fg,
                loss_tensor_names["fg_pred"]: fg_pred,
                loss_tensor_names["maxima"]: maxima,
                loss_tensor_names["gt_fg"]: gt_fg,
                loss_tensor_names["emst"]: emst,
                loss_tensor_names["edges_u"]: edges_u,
                loss_tensor_names["edges_v"]: edges_v,
                loss_tensor_names["ratio_pos"]: ratio_pos,
                loss_tensor_names["ratio_neg"]: ratio_neg,
                loss_tensor_names["dist"]: dist,
                loss_tensor_names["num_pos_pairs"]: num_pos_pairs,
                loss_tensor_names["num_neg_pairs"]: num_neg_pairs,
            },
            gradients={
                mknet_tensor_names["embedding"]: gradient_embedding,
                mknet_tensor_names["fg"]: gradient_fg,
            },
            save_every=checkpoint_every,
            summary="Merge/MergeSummary:0",
            log_dir="tensorflow_logs",
        )
        + gp.PrintProfilingStats(every=profile_every)
        + gp.Snapshot(
            additional_request=snapshot_request,
            output_filename="snapshot_{}_{}.hdf".format(
                int(np.min(seperate_distance)), "{id}"
            ),
            dataset_names={
                # raw data
                raw: "volumes/raw",
                # labeled data
                labels: "volumes/labels",
                # trees
                skeletonization: "points/skeletonization",
                consensus: "points/consensus",
                matched: "points/matched",
                # output volumes
                embedding: "volumes/embedding",
                fg: "volumes/fg",
                maxima: "volumes/maxima",
                gt_fg: "volumes/gt_fg",
                fg_pred: "volumes/fg_pred",
                gradient_embedding: "volumes/gradient_embedding",
                gradient_fg: "volumes/gradient_fg",
                # output trees
                emst: "emst",
                edges_u: "edges_u",
                edges_v: "edges_v",
                # output debug data
                ratio_pos: "ratio_pos",
                ratio_neg: "ratio_neg",
                dist: "dist",
                num_pos_pairs: "num_pos_pairs",
                num_neg_pairs: "num_neg_pairs",
                loss_weights: "volumes/loss_weights",
            },
            every=snapshot_every,
        )
    )

    with gp.build(pipeline):
        for _ in range(num_iterations):
            pipeline.request_batch(request)

def train(
    n_iterations,
    setup_config,
    emb_mknet_tensor_names,
    emb_loss_tensor_names,
    fg_mknet_tensor_names,
):
    if setup_config["FUSION_PIPELINE"]:
        train_fusion_pipeline(
            n_iterations,
            setup_config,
            emb_mknet_tensor_names,
            emb_loss_tensor_names,
            fg_mknet_tensor_names,
        )
    else:
        train_simple_pipeline(
            n_iterations,
            setup_config,
            emb_mknet_tensor_names,
            emb_loss_tensor_names,
            fg_mknet_tensor_names,
        )


import cv2
import numpy as np
import json

image = cv2.imread(r'C:\Users\aminb\Desktop\FIBO\Image\Moduel_image\messageImage_1604660857654.jpg')
# image = cv2.medianBlur(image,9)
# kernel = np.array([[-1,-1,-1],
#                     [-1, 9,-1],
#                     [-1,-1,-1]])
# sharpened = cv2.filter2D(image, -1, kernel)
# image=cv2.fastNlMeansDenoising(sharpened, None, 10, 2, 21)

image_gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

resolution_X = image.shape[0]
resolution_Y = image.shape[1]

def update(x):
    pass

def add_point_to_list(hie):
    global list_contours_symbol, list_contours_box_symbol, list_contours_path
    for i in hie:  #ADD list_contours
        Parent_check = (i[3] == 0) # check in case
        Child_check_symbol = (i[2] != -1) # Symbol
        Child_check_path = (i[2] == -1) # PATH
        if Parent_check:
            if Child_check_symbol:
                list_contours_box_symbol.append(hie.index(i))
            if Child_check_path:
                list_contours_path.append(hie.index(i))

cv2.namedWindow("image_blur")
Kernel_blur = 1
cv2.createTrackbar("Kernel_blur","image_blur",3,7,update)

cv2.namedWindow("image_edge")
Canny_Thres_1 = 40
cv2.createTrackbar("Canny_Thres_1","image_edge",Canny_Thres_1,255,update)
Canny_Thres_2 = 100
cv2.createTrackbar("Canny_Thres_2","image_edge",Canny_Thres_2,255,update)

cv2.namedWindow("image_morp")
Kernel_morp = 1
cv2.createTrackbar("Kernel_morp","image_morp",Kernel_morp,7,update)
Mode_morp = 1
cv2.createTrackbar("Mode_morp","image_morp",Mode_morp,3,update)
iterations = 1
cv2.createTrackbar("iterations","image_morp",iterations,4,update)

cv2.namedWindow("image_contour")
contour_use = []
maxArea = int(resolution_X*resolution_Y/50)
minArea = 0
cv2.createTrackbar("maxArea","image_contour",6045,20000,update)
cv2.createTrackbar("minArea","image_contour",3325,20000,update)

list_all_point_path = []
list_contours_symbol = []
list_contours_box_symbol = []
list_contours_path = []
list_point_connected = []

while(1):
    image_blur = cv2.GaussianBlur(image_gray , (Kernel_blur,Kernel_blur), 0)
    cv2.imshow("image_blur",image_blur)

    image_edge = cv2.Canny(image_blur,Canny_Thres_1,Canny_Thres_2)
    cv2.imshow("image_edge",image_edge)
    Kernel_morp_use = cv2.getStructuringElement(cv2.MORPH_CROSS, (Kernel_morp,Kernel_morp))
    # Kernel_morp_use  = cv2.GaussianBlur(image_gray , (Kernel_morp,Kernel_morp), 0)
    image_morp = cv2.dilate(image_edge,Kernel_morp_use,iterations = iterations)
    if Mode_morp == 1:
        image_morp = cv2.morphologyEx(image_morp, cv2.MORPH_CLOSE, Kernel_morp_use)
    elif Mode_morp == 2:
        image_morp = cv2.morphologyEx(image_morp, cv2.MORPH_CROSS, Kernel_morp_use)
    elif Mode_morp == 3:
        image_morp = cv2.morphologyEx(image_morp, cv2.MORPH_OPEN, Kernel_morp_use)
    cv2.imshow("image_morp",image_morp)

    contours, hierarchy = cv2.findContours(image_morp , cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    for i in range(0, len(contours)):
        area = int(cv2.contourArea(contours[i]))
        if area >= minArea:
            if area <= maxArea*100:
                contour_use.append(i)
    black_image = np.zeros((resolution_X, resolution_Y, 1), np.uint8)

    for i in contour_use:
        cv2.drawContours(black_image,contours,i,(255,255,255))
    black_image = cv2.dilate(black_image,Kernel_morp_use,iterations = iterations)
    cv2.imshow("image_contour",black_image)

    contour1, hie = cv2.findContours(black_image , cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
    black_image1 = np.zeros((resolution_X, resolution_Y, 1), np.uint8)
    cv2.drawContours(black_image1,contour1,-1,(255,255,255))
    blacked = np.zeros((resolution_X, resolution_Y, 1), np.uint8)
    filled_contour = cv2.fillPoly(blacked, [contour1[1]], color=(255,255,255))
    _,filled_path_binary = cv2.threshold(filled_contour,0,255,cv2.THRESH_BINARY+cv2.THRESH_OTSU)
    thin_image = cv2.ximgproc.thinning(filled_path_binary,thinningType=cv2.ximgproc.THINNING_ZHANGSUEN)
    cv2.imshow("thin_image",thin_image)
    # black_image1 = cv2.dilate(black_image1,Kernel_morp_use,iterations = iterations)
    cv2.imshow("image_Kuy",black_image1)
    # print(hie)
    add_point_to_list(hie.tolist()[0])
    # print(list_contours_box_symbol)
    # print("Kuy")
    print(len(contour1))

    contour_use = []
    list_contours_path = []
    list_contours_box_symbol = []
    k = cv2.waitKey(1) & 0xFF
    if k == 27:
        break
    Kernel_blur = ((cv2.getTrackbarPos("Kernel_blur","image_blur")+1)*2)-1

    Canny_Thres_1 = cv2.getTrackbarPos("Canny_Thres_1","image_edge")
    Canny_Thres_2 = cv2.getTrackbarPos("Canny_Thres_2","image_edge")

    Kernel_morp = ((cv2.getTrackbarPos("Kernel_morp","image_morp")+1)*2)-1
    Mode_morp = cv2.getTrackbarPos("Mode_morp","image_morp")
    iterations = cv2.getTrackbarPos("iterations","image_morp")

    minArea = cv2.getTrackbarPos("minArea","image_contour")
    maxArea = cv2.getTrackbarPos("maxArea","image_contour")

    data = {}
    data['Parameter'] = []
    data['Parameter'].append({
        'Kernel_blur': (int(((Kernel_blur+1)/2)-1)),
        'Canny_Thres_1': Canny_Thres_1,
        'Canny_Thres_2': Canny_Thres_2,
        'Kernel_morp': (int(((Kernel_morp+1)/2)-1)),
        'Mode_morp': Mode_morp,
        'iterations': iterations,
        'minArea:' : minArea,
        'maxArea': int(maxArea/100)
    })

    # data['blur'].append({
    #     'name': 'Larry',
    #     'website': 'google.com',
    #     'from': 'Michigan'
    # })
    # data['blur'].append({
    #     'name': 'Tim',
    #     'website': 'apple.com',
    #     'from': 'Alabama'
    # })

with open(r'C:\Users\aminb\Desktop\FIBO\Image\Moduel_image\parameter.json', 'w') as outfile:
    json.dump(data, outfile)


"""Trace function invocation using decorator."""
from __future__ import absolute_import

import functools
import logging
import threading

_LOGGER = logging.getLogger(__name__)
_TRACE = threading.local()

def disable(func):
    """Decorator to disable trace on a given function."""
    return _enable_trace(func, False)

def enable(func):
    """Decorator to enable trace on a given function."""
    return _enable_trace(func, True)

def _enable_trace(func, enabled):
    """Returns decorator to enable/disable tracing on the function."""
    @functools.wraps(func)
    def _wrap(*args, **kwargs):
        """Decorator to disable function trace."""
        prev_state = getattr(_TRACE, 'enabled', True)
        _TRACE.enabled = enabled
        try:
            return func(*args, **kwargs)
        finally:
            _TRACE.enabled = prev_state

    return _wrap

def trace_calls(frame, event, arg):
    """Trace calls using sys.settrace."""
    # Ignore line events.
    if event == 'line':
        return

    trace_enabled = getattr(_TRACE, 'enabled', None)
    if trace_enabled is None:
        trace_enabled = True

    if not trace_enabled:
        return

    code = frame.f_code
    func_name = code.co_name
    filename = code.co_filename

    if func_name == 'write':
        # Ignore write() calls from print statements.
        return

    if func_name.startswith('_'):
        # Ignore private functions.
        return

    # Ignore non-treadmill modules.
    if filename.find('/treadmill/') < 0:
        return

    if event == 'call':
        _LOGGER.debug('%s: %s ( %s )', filename, func_name, frame.f_locals)
    elif event == 'return':
        _LOGGER.debug('%s => %s', func_name, arg)
    elif event == 'exception':
        exc_type, exc_value, _traceback = arg
        line_no = frame.f_lineno
        _LOGGER.debug('exception: %s[%s]: %s, %s', filename, line_no,
                      exc_type.__name__, exc_value)
    else:
        # Ignore c_calls/c_return/c_exception.
        pass

    return trace_calls


# -*- coding:utf-8 -*-
'''
__author__ = 'XD'
__mtime__ = 2021/1/18
__project__ = Pon-Sol2
Fix the Problem, Not the Blame.
'''
# -*- coding:utf-8 -*-
# 文件写法参考：https://github.com/dls-controls/python-logging-configuration/blob/master/logconfig.py
# 具体配置自写
import json
import yaml
import logging
import logging.config
import os

default_config = {
    "version": 1,  # 目前只有 1 有效，用于以后兼容性
    "incremental": False,  # 是否在运行中时修改配置, 默认 False
    "disable_existing_loggers": True,  # 是否禁用任何非根的所有 Logger, 默认 False
    "formatters": {  # 格式化生成器(格式器)
        "default": {
            "format": "%(name)s %(asctime)s [%(filename)s %(funcName)s()] <%(levelname)s>: %(message)s",
        },
        "brief": {
            "format": "%(name)s [%(funcName)s()] <%(levelname)s>: %(message)s",
        }
    },
    "filters": {},  # 过滤器，需要自定义类，一般不会用到
    "handlers": {
        "console": {  # 控制台
            "class": "logging.StreamHandler",
            "formatter": "brief",
            "level": "DEBUG",
            "stream": "ext://sys.stdout",
        },
        "file_debug": {  # 输出到文件
            "class": "logging.handlers.RotatingFileHandler",
            "formatter": "default",
            "level": "DEBUG",
            "filename": "ponsol2_predictor_debug.log",  # 必选, 文件名称
            "encoding": "utf8",
            "maxBytes": 10485760,  # 日志文件最大个数 1024B * 1024 * 10 = 10MB
            "backupCount": 10,  # 日志文件最大个数
        },
        "file_info": {  # 输出到文件
            "class": "logging.handlers.RotatingFileHandler",
            "formatter": "default",
            "level": "INFO",
            "filename": "ponsol2_predictor_info.log",  # 必选, 文件名称
            "encoding": "utf8",
            "maxBytes": 10485760,  # 日志文件最大个数 1024B * 1024 * 10 = 10MB
            "backupCount": 10,  # 日志文件最大个数
        },
    },
    "loggers": {
        "ponsol": {
            "level": "DEBUG",
            "handlers": ["console", "file_debug", "file_info"],
            "propagate": False,  # 是否传给父级
        }
    },
}

def setup_logging(
        default_log_config=None,
        is_yaml=True,
        default_level=logging.INFO,
        env_key='LOG_CFG',
):
    """Setup logging configuration
    Call this only once from the application main() function or __main__ module!
    This will configure the python logging module based on a logging configuration
    in the following order of priority:
       1. Log configuration file found in the environment variable specified in the `env_key` argument.
       2. Log configuration file found in the `default_log_config` argument.
       3. Default log configuration found in the `logconfig.default_config` dict.
       4. If all of the above fails: basicConfig is called with the `default_level` argument.
    Args:
        default_log_config (Optional[str]): Path to log configuration file.
        env_key (Optional[str]): Environment variable that can optionally contain
            a path to a configuration file.
        default_level (int): logging level to set as default. Ignored if a log
            configuration is found elsewhere.
        is_yaml (bool): weather config file is a yaml file
    Returns: None
    """
    dict_config = None
    logconfig_filename = default_log_config
    env_var_value = os.getenv(env_key, None)

    if env_var_value is not None:
        logconfig_filename = env_var_value

    if default_config is not None:
        dict_config = default_config

    if logconfig_filename is not None and os.path.exists(logconfig_filename):
        with open(logconfig_filename, 'rt', encoding="utf8") as f:
            if is_yaml:
                file_config = yaml.load(f, Loader=yaml.FullLoader)
            else:
                file_config = json.load(f)
        if file_config is not None:
            dict_config = file_config

    if dict_config is not None:
        logging.config.dictConfig(dict_config)
    else:
        logging.basicConfig(level=default_level)

if __name__ == '__main__':
    with open("./logconfig.yaml", "w") as f:
        yaml.dump(default_config, f)


# uncompyle6 version 3.7.4
# Python bytecode 3.6 (3379)
# Decompiled from: Python 3.6.9 (default, Apr 18 2020, 01:56:04)
# [GCC 8.4.0]
# Embedded file name: /usr/local/lib/python3.6/dist-packages/pyxrd/specimen/views/specimens.py
# Compiled at: 2020-03-07 03:51:50
# Size of source mod 2**32: 3038 bytes
from pkg_resources import resource_filename
from pyxrd.generic.views import DialogView, BaseView, HasChildView
from pyxrd.goniometer.views import InlineGoniometerView
from pyxrd.generic.views.line_views import CalculatedLinePropertiesView, ExperimentalLinePropertiesView

class SpecimenView(DialogView, HasChildView):
    title = 'Edit Specimen'
    subview_builder = resource_filename(__name__, '../glade/specimen.glade')
    subview_toplevel = 'edit_specimen'
    resizable = False
    widget_format = 'specimen_%s'
    widget_groups = {'full_mode_only': [
                        'specimen_display_calculated',
                        'specimen_display_stats_in_lbl',
                        'specimen_display_phases',
                        'vbox_calculated_data_tv',
                        'lbl_specimen_calculated',
                        'vbox_exclusion_ranges_tv',
                        'lbl_tabexclusions',
                        'general_separator',
                        'specimen_display_residuals',
                        'specimen_display_derivatives']}
    gonio_container = widget_format % 'goniometer'
    gonio_view = None
    calc_line_container = widget_format % 'calc_line'
    calc_line_view = None
    exp_line_container = widget_format % 'exp_line'
    exp_line_view = None

    def __init__(self, *args, **kwargs):
        (super(SpecimenView, self).__init__)(*args, **kwargs)
        self._init_child_views()
        self._add_child_views()

    _children_ready = False

    def _init_child_views(self):
        if not self._children_ready:
            self.gonio_view = InlineGoniometerView(parent=self)
            self.calc_line_view = CalculatedLinePropertiesView(parent=self)
            self.exp_line_view = ExperimentalLinePropertiesView(parent=self)
            self._children_ready = True

    def _add_child_views(self):
        self._init_child_views()
        top = self.gonio_view.get_top_widget()
        self._add_child_view(top, self[self.gonio_container])
        top = self.calc_line_view.get_top_widget()
        self._add_child_view(top, self[self.calc_line_container])
        top = self.exp_line_view.get_top_widget()
        self._add_child_view(top, self[self.exp_line_container])

    def set_layout_mode(self, state):
        super(SpecimenView, self).set_layout_mode(state)
        if self._children_ready:
            self.gonio_view.set_layout_mode(state)
            self.calc_line_view.set_layout_mode(state)
            self.exp_line_view.set_layout_mode(state)

class StatisticsView(BaseView):
    builder = resource_filename(__name__, '../specimen/glade/statistics.glade')
    top = 'statistics_box'

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


def isDivisible(num):
    sumOfEvenDigits = 0
    sumOfOddDigits = 0
    txt1 = " is a multiple of 11."
    txt2 = " is not a multiple of 11."

    for index in range(len(num)):

        if (index % 2) is 0:
                sumOfEvenDigits += int(num[index])
        else:
            sumOfOddDigits += int(num[index])

    if ((sumOfEvenDigits - sumOfOddDigits) % 11) is not 0:
        return num + txt2
    else:
        return num + txt1

while(1):
    num = str(input("Enter a number:"))
    if num is "0":
        break
    else:
        print(isDivisible(num))


# -*- coding: utf-8 -*-
"""
Created on Mon May 29 09:33:06 2017

@author: Jaywan Chung

updated on Mon May 2017
"""

import copy
from functools import lru_cache
import unittest

class HashDict:
    """Create an immutable dict.

    Arithmetic operations (+,-,*,/) are allowed if the items in HashDict allows those operations.
    """

    _dict = {}

    def __hash__(self):
        return hash( frozenset(self._dict.items()) )

    def __eq__(self, other):
        """ self == other """
        if isinstance(other, HashDict):
            return self._dict == other._dict
        elif isinstance(other, dict):
            return self._dict == other
        else:
            return False

    def to_dict(self):
        """ return a dictionary """
        return self._dict.copy()

    def to_string(self):
        return str( self.to_dict() )

    def __repr__(self):
        return 'HashDict: ' + self.to_string()

    def __str__(self):
        return self.to_string()

    def __init__(self, a_dict):
        if isinstance(a_dict, dict):
            self._dict = a_dict.copy()
        elif isinstance(a_dict, HashDict):
            self._dict = a_dict.to_dict()
        else:
            raise TypeError('Cannot create a HashDict.')

    @lru_cache()
    def _add_hashdicts( hashdict1, hashdict2 ):
        """ Add two HashDicts and return a HashDict. """
        dict1 = hashdict1._dict
        dict2 = hashdict2._dict
        all_keys = list(dict1.keys())
        for key in dict2.keys():
            if not(key in all_keys):
                all_keys.append(key)
        added_dict = {}
        for key in all_keys:
            added_dict[key] = (dict1.get(key) or 0) + (dict2.get(key) or 0)
        return HashDict( added_dict )

    def __add__(self, other):
        """ self + other """
        try:
            other = HashDict(other)
            return HashDict._add_hashdicts( self, other )
        except TypeError: # treat as an element
            self_dict = self.to_dict()
            for key in self_dict.keys():
                try:
                    self_dict[key] += other
                except:
                    raise TypeError("Cannot be added to a HashDict.")
            return HashDict( self_dict )

    def __radd__(self, other):
        """ other + self = self + other """
        return self + other   # additive operation is commutative

    @lru_cache()
    def _mul_hashdicts( hashdict1, hashdict2 ):
        """ Multiply two dicts and return a HashDict. """
        dict1 = hashdict1._dict
        dict2 = hashdict2._dict
        mul_dict = {}
        for key in dict1.keys():
            val = dict2.get(key)
            if not(val == None):
                mul_dict[key] = dict1[key] * val
        return HashDict( mul_dict )

    def __mul__(self, other):
        """
        self * other
        if a key is contained only in one HashDict (self or other), treat its value as 0 so omit the key.
        """
        try:
            other = HashDict(other)
            return HashDict._mul_hashdicts( self, other )
        except TypeError:  # treat as an element
            self_dict = self.to_dict()
            for key in self_dict.keys():
                try:
                    self_dict[key] *= other
                except:
                    raise TypeError("Cannot be multiplied to a HashDict.")
            return HashDict( self_dict )

    def __rmul__(self, other):
        """ other * self = self * other """
        return self * other   # multiplicative operation is commutative

    def __neg__(self):
        """ -self = self * (-1) """
        return self * (-1)

    def __sub__(self, other):
        """ self - other = self + (-other) """
        return self + (-other)

    @lru_cache()
    def _div_hashdicts( hashdict1, hashdict2 ):
        """ Divide first dict by second dict and return a HashDict. """
        dict1 = hashdict1._dict
        dict2 = hashdict2._dict
        div_dict = {}
        for key in dict1.keys():
            val = dict2.get(key)
            if not(val == None):
                div_dict[key] = dict1[key] / val
        return HashDict( div_dict )

    def __truediv__(self, other):
        """
        self / other
        if a key is contained only in one HashDict (self or other), omit the key.
        """
        try:
            other = HashDict(other)
            return HashDict._div_hashdicts( self, other )
        except TypeError:  # treat as an element
            self_dict = self.to_dict()
            for key in self_dict.keys():
                try:
                    self_dict[key] /= other
                except:
                    raise TypeError("Cannot divide with a HashDict.")
            return HashDict( self_dict )

    def __rtruediv__(self, other):
        """
        other / self
        if a key is contained only in one HashDict (self or other), omit the key.
        """
        try:
            other = HashDict(other)
            return HashDict._div_hashdicts( other, self )
        except TypeError:  # treat as an element
            self_dict = self.to_dict()
            for key in self_dict.keys():
                try:
                    self_dict[key] = other / self_dict[key]
                except:
                    raise TypeError("Cannot divide with a HashDict.")
            return HashDict( self_dict )

    def __iter__(self):
        return self._dict.__iter__()

    def __len__(self):
        return len(self._dict)

    def __getitem__(self, key):
        return self._dict[key]

    def items(self):
        return self._dict.items()

    def get(self, key, default=None):
        return self._dict.get(key, default)

    def keys(self):
        return self._dict.keys()

    def values(self):
        return self._dict.values()

    def drop_zeros(self):
        self_dict = self.to_dict()
        for key in self.keys():
            if self.get(key) == 0:
                self_dict.pop(key)
        return HashDict(self_dict)

    def project_to(self, keys_tuple, default_value=0):
        self_dict = self.to_dict()
        arranged_list = []
        for key in keys_tuple:
            arranged_list.append( (key, (self_dict.get(key) or default_value)) )
        return HashDict( dict(arranged_list) )

    @lru_cache()
    def divide_and_project_to( self, hashdict ):
        """
        Divide the exponents of "hashdict1" by the exponents of "hashdict2".
        Only the keys in "hashdict2" is considered. And the missing key in "hashdict1" is assumed to have the zero exponent.
        For example: {'kg':3, 's':2} / {'m':1, 's':2} = {'m':0, 's':1}
        Returns a HashDict.
        """
        usually_divided = self / hashdict
        result = usually_divided.project_to( hashdict.keys() )
        return result

    def pop(self, key, default=None):
        """
        Return a tuple consists of the corresponding item to the given key and a HashDict the key is removed from.
        REMEMBER a HashDict is immutable.
        """
        self_dict = self.to_dict()
        item = self_dict.pop(key, default)
        return (item, HashDict(self_dict))

    def copy(self):
        """ Return a shallow of copy of self. """
        return copy.copy(self)

class TestHashDict(unittest.TestCase):
    """
    A Test case for the HashDict class.
    """

    def setUp(self):
        self.dct = HashDict( {'kg':1, 'm':2, 's':-1} )
    def test__add__(self):
        dct1 = HashDict({'kg':1, 'm':2, 's':-1})
        dct2 = HashDict({'kg':-1, 'm':0, 's':-1})
        self.assertEqual(str(dct1+dct2), "{'kg': 0, 'm': 2, 's': -2}")
    def test__mul__(self):
        self.assertEqual(str(self.dct * -3), "{'kg': -3, 'm': -6, 's': 3}")
        dct1 = HashDict({'kg':1, 'm':2, 's':-1})
        dct2 = HashDict({'kg':-1, 'm':0, 's':-1})
        self.assertEqual(str(dct1 * dct2), "{'kg': -1, 'm': 0, 's': 1}")
    def test__sub__(self):
        dct1 = HashDict( {'kg':3, 's':2} )
        dct2 = HashDict( {'m':1, 's':2} )
        result = dct1 - dct2
        self.assertEqual( str(result), "{'kg': 3, 's': 0, 'm': -1}" )
    def test_drop_zeros(self):
        dct = HashDict({'kg':1, 'm':2, 's':0})
        self.assertEqual(str(dct.drop_zeros()), "{'kg': 1, 'm': 2}")
    def test_divide_and_project_to(self):
        dct1 = HashDict({'kg':3, 's':2})
        dct2 = HashDict({'m':1, 's':2})
        result = dct1.divide_and_project_to( dct2 )
        self.assertEqual( str(result), "{'m': 0, 's': 1.0}" )
    def test_pop(self):
        dct = HashDict({'kg':3, 's':2})
        (item, dic) = dct.pop('s')
        self.assertEqual( item, 2 )
        self.assertEqual( str(dic), "{'kg': 3}" )

if __name__=='__main__':
    # unit test
    suite = unittest.defaultTestLoader.loadTestsFromTestCase( TestHashDict )
    unittest.TextTestRunner().run(suite)


import re
from struct import pack

from elf import valid_address, valid_addresses, get_offset, get_all_addresses
from php7.at_start.entry import get_data, get_glob_segments, init
from php7.at_start.my_globals import *
from php7.sym_tab.zend_execute_data import ZendExecuteData
from php7.zval_types.hashtables.hashtable_zvalue import HashTableZvalue

"""
hlavna struktura, ktoru bude aplikacia hladat

obsahuje:
    smernik na strukturu zend_executor_globals
        tabulky:
            symbolov mainu
            includnutych suborov
            perzistentnych resourcov
            neperzistentnych resourcov
        smerniky na tabulky:
            tried
            funkcii
            konstant
        smernik na strukturu zend_execute_data

validna ak:
    struktura zend_executor globals na smerniku je validna
        su validne tabulky:
            symbolov
            includnutych suborov
        su validne tabulky na smernikoch:
            konstant
            funkcii
            tried
"""

"""
struct _zend_executor_globals {
    zval uninitialized_zval;
    zval error_zval;

    /* symbol table cache */
    zend_array *symtable_cache[SYMTABLE_CACHE_SIZE];
    zend_array **symtable_cache_limit;
    zend_array **symtable_cache_ptr;

    zend_array symbol_table;                            /* main symbol table */

    HashTable included_files;                           /* files already included */

    JMP_BUF *bailout;

    int error_reporting;
    int exit_status;

    HashTable *function_table;                          /* function symbol table */
    HashTable *class_table;	                            /* class table */
    HashTable *zend_constants;                          /* constants table */

    zval          *vm_stack_top;
    zval          *vm_stack_end;
    zend_vm_stack  vm_stack;

    struct _zend_execute_data *current_execute_data;
    zend_class_entry *fake_scope;                       /* used to avoid checks accessing properties */

    zend_long precision;

    int ticks_count;

    HashTable *in_autoload;
    zend_function *autoload_func;
    zend_bool full_tables_cleanup;

    /* for extended information support */
    zend_bool no_extensions;

    zend_bool vm_interrupt;
    zend_bool timed_out;
    zend_long hard_timeout;

#ifdef ZEND_WIN32
    OSVERSIONINFOEX windows_version_info;
#endif

    HashTable regular_list;
    HashTable persistent_list;
    ...
}
"""

symbol_table_a = 2 * zval_size + 3 * 8
included_files_a = symbol_table_a + array_size
jmp_buf_a = included_files_a + array_size
ftaa = jmp_buf_a + 16
ctaa = ftaa + 8
zctaa = ctaa + 8
ex_data_aa = zctaa + 32
reg_list_a = ex_data_aa + 64
per_list_a = reg_list_a + array_size

class ExecutorGlobals:
    def __init__(self, maps=None) -> None:
        self.__proc_pid_maps = maps
        if self.__proc_pid_maps is None:
            segments = get_glob_segments()
        else:
            segments = self.get_segments_from_file()
        for address in get_all_addresses(segments):
            # for address in [0xa5e030]: # 9169
            # for address in [0xf23578]: # 2617
            # for address in [0xf23578]: # 2875
            # for address in [0xf23578]: # 2382
            try:
                self.class_table = None
                self.function_table = None
                self.symbol_table = None
                self.included_files_table = None
                self.regular_list = None
                self.persistent_list = None
                self.zend_constants_table = None
                self.execute_data = None

                self.symbol_table = HashTableZvalue(pack('Q', symbol_table_a + address))
                if not self.symbol_table.is_valid():
                    continue

                self.included_files_table = HashTableZvalue(pack('Q', included_files_a + address))
                if not self.included_files_table.is_valid():
                    continue

                function_table_a, class_table_a, zend_constants_a, execute_data_a = \
                    self.get_pointer(ftaa + address), self.get_pointer(ctaa + address), \
                    self.get_pointer(zctaa + address), self.get_pointer(ex_data_aa + address)

                if None in [function_table_a, class_table_a, zend_constants_a, ex_data_aa]:
                    continue

                if not valid_addresses([reg_list_a + address, per_list_a + address], get_glob_segments()):
                    continue

                if valid_addresses([function_table_a, class_table_a, zend_constants_a], get_glob_segments()):
                    self.function_table = HashTableZvalue(pack('Q', function_table_a))
                    if not self.function_table.is_valid():
                        continue

                    self.class_table = HashTableZvalue(pack('Q', class_table_a))
                    if not self.class_table.is_valid():
                        continue

                    self.zend_constants_table = HashTableZvalue(pack('Q', zend_constants_a))
                    if not self.zend_constants_table.is_valid():
                        continue

                    self.regular_list = HashTableZvalue(pack('Q', reg_list_a + address))
                    self.persistent_list = HashTableZvalue(pack('Q', per_list_a + address))
                    if self.check_valid(
                            [self.symbol_table,
                             self.included_files_table,
                             self.function_table,
                             self.class_table,
                             self.zend_constants_table]):
                        print(hex(address))
                        self.execute_data = ZendExecuteData(pack('Q', execute_data_a))
                        break
            except RuntimeError:
                pass

    def get_pointer(self, addr):
        if not valid_address(addr, get_glob_segments()):
            return None
        return unpack('Q', get_data(get_offset(addr, get_glob_segments()), 8))[0]

    def check_valid(self, hashtables: [HashTableZvalue]):
        result = True
        for hashtable in hashtables:
            result = result and hashtable.is_valid()
        return result

    def get_segments_from_file(self):
        regex = r"^([a-fA-F0-9]+)\-([a-fA-F0-9]+)[^/]*$"
        textfile = open(self.__proc_pid_maps, 'r')
        matches = []
        reg = re.compile(regex)
        for line in textfile:
            matches += reg.findall(line[:-1])
        textfile.close()
        segments = []
        for begin, end in matches:
            segments.append({"virt_addr_begin": int(begin, 16), "virt_addr_end": int(end, 16)})
        return segments

if __name__ == "__main__":
    filename = '../dumps/php7/core.2617'
    # filename = '../dumps/php7/core.9169'
    # filename = '../dumps/php7/core.5769'
    init(filename)

    eg = ExecutorGlobals()
    print(eg.execute_data)


class Solution:
    def addStrings(self, num1: str, num2: str) -> str:
        return_string = ""
        pointer1 = len(num1) - 1
        pointer2 = len(num2) - 1
        propagate = 0
        while pointer1 >= 0 or pointer2 >= 0:
            if pointer1 >= 0:
                num1_digit = int(num1[pointer1])
            else:
                num1_digit = 0
            if pointer2 >= 0:
                num2_digit = int(num2[pointer2])
            else:
                num2_digit = 0
            sum_digits = num1_digit + num2_digit + propagate
            propagate = sum_digits // 10
            digit_req = sum_digits % 10
            return_string = str(digit_req) + return_string
            pointer1 -= 1
            pointer2 -= 1
        if propagate > 0:
            return_string = "1" + return_string
        return return_string


'''
1037. Valid Boomerang
Given an array points where points[i] = [xi, yi] represents a point on the X-Y plane, return true if these points are a boomerang.

A boomerang is a set of three points that are all distinct and not in a straight line.

Example 1:

Input: points = [[1,1],[2,3],[3,2]]
Output: true
Example 2:

Input: points = [[1,1],[2,2],[3,3]]
Output: false

Constraints:

points.length == 3
points[i].length == 2
0 <= xi, yi <= 100
'''
class Solution(object):
    def isBoomerang(self, points):
        """
        :type points: List[List[int]]
        :rtype: bool
        """
        # y = mx + c
        # m = (y2 - y1) / (x2 - x1)
        # c = x1.y2 - x2.y1
        (x0, y0), (x1, y1), (x2, y2) = points
        return (y2 - y1) * (x0 - x1) != (x2 - x1) * (y0 - y1)

obj = Solution()
print(obj.isBoomerang([[0,0],[0,2],[2,1]]))