import sys
import uuid
import json
import traceback

from akashic.arules.transpiler import Transpiler

from akashic.util.type_converter import string_to_py_type
from akashic.exceptions import AkashicError, ErrType

from akashic.system.rules.generic_rule import GENERIC_RULE
from akashic.system.rules.query_rule import QUERY_RULE

from akashic.ads.data_provider import FactGenType

class DataBridge(object):
    """ DataBridge class

    We use this class to store CRUD and data related python
    functions called by CLIPS environment
    """

    def __init__(self, data_providers, env_provider, debug=True):
        self.debug = debug

        self.data_providers = data_providers
        self.env_provider = env_provider

        self.data_providers_map = {}
        for dp in self.data_providers:
            self.data_providers_map[dp.dsd.model_id] = dp

        # If num_of_args are -1, -2, -3,
        # then it is actually 'more than 1,2,3..'
        self.exposed_functions= [
            {
                "function":     self.create_func,
                "num_of_args":  -1,
                "return_type":  "INTEGER"
            },
            {
                "function":     self.return_func,
                "num_of_args":  -1,
                "return_type":  "INTEGER"
            },
            {
                "function":     self.update_func,
                "num_of_args":  -1,
                "return_type":  "INTEGER"
            },
            {
                "function":     self.delete_func,
                "num_of_args":  -1,
                "return_type":  "INTEGER"
            },
            {
                "function":     self.process_query,
                "num_of_args":  -1,
                "return_type":  "INTEGER"
            }
        ]

    #### func caller methods #####

    def create_func(self, *args):
        try:
            self.create_func_call(*args)
            print("FUNC 'create_func' COMPLETED")
        except:
            print("Error in data bridge - create_func: ", sys.exc_info()[0])
            traceback.print_exc()

    def return_func(self, *args):
        try:
            self.return_func_call(*args)
            print("FUNC 'return_func' COMPLETED")
        except:
            print("Error in data bridge - return_func: ", sys.exc_info()[0])
            traceback.print_exc()

    def update_func(self, *args):
        try:
            self.update_func_call(*args)
            print("FUNC 'update_func' COMPLETED")
        except:
            print("Error in data bridge - update_func: ", sys.exc_info()[0])
            traceback.print_exc()

    def delete_func(self, *args):
        try:
            self.delete_func_call(*args)
            print("FUNC 'delete_func' COMPLETED")
        except:
            print("Error in data bridge - delete_func: ", sys.exc_info()[0])
            traceback.print_exc()

    def process_query(self, *args):
        try:
            self.process_query_call(*args)
            print("FUNC 'process_query' COMPLETED")
        except:
            print("Error in data bridge - process_query: ", sys.exc_info()[0])
            traceback.print_exc()

    def refresh_data_providers(self, data_providers):
        self.data_providers = data_providers
        self.data_providers_map = {}
        for dp in self.data_providers:
            self.data_providers_map[dp.dsd.model_id] = dp

    def data_arg_list_to_request_body(self, arg_list):
        json_construct = {}
        i = 0
        l = len(arg_list)
        while i < l:
            field_name = arg_list[i]
            field_value = arg_list[i+1]
            to_type = arg_list[i+2]

            json_construct[field_name] = string_to_py_type(field_value,
                                                           to_type)
            i += 3
        return json_construct

    def ref_arg_list_to_url_map(self, arg_list, dsd_api_object):
        url_map_args = {}
        i = 0
        l = len(arg_list)
        while i < l:
            for ref in dsd_api_object.ref_models:
                if arg_list[i] == ref.url_placement:
                    url_map_args[ref.url_placement] = arg_list[i + 1]

            i += 2

        return url_map_args

    def print_args(self, args, header):
        if self.debug:
            print("\n-------------------" + str(header) + " START")
            for a in args:
                print(a)
            print("\n-------------------" + str(header) + " END")

    def create_func_call(self, *args):
        args = map(lambda arg: arg.replace('"', ''), args)
        args = list(args)

        self.print_args(args, "CREATE")

        MODEL_ID_POS       = 0
        REFLECT_INFO_POS   = 2
        DATA_LEN_POS       = 4
        DATA_START_POS     = 5

        data_provider = self.data_providers_map[args[MODEL_ID_POS]]
        reflect_on_web = string_to_py_type(args[REFLECT_INFO_POS], "BOOLEAN")

        data_len = string_to_py_type(args[DATA_LEN_POS], "INTEGER")
        data_json_construct = self.data_arg_list_to_request_body(
            args[DATA_START_POS:DATA_START_POS+data_len]
        )

        if reflect_on_web:
            REF_LEN_POS = DATA_START_POS + data_len + 1
            ref_len = string_to_py_type(args[REF_LEN_POS], "INTEGER")

            REF_START_POS = REF_LEN_POS + 1
            url_map_args = self.ref_arg_list_to_url_map(
                args[REF_START_POS:REF_START_POS+ref_len],
                data_provider.dsd.apis.create
            )

            print("DATA_JSON_CONSTRUCT")
            print(json.dumps(data_json_construct));
            response_obj = data_provider.create(data_json_construct,
                                                **url_map_args)

            # Create full CLIPS fact from response and add it to CLIPS env
            clips_fact = data_provider.generate_one_clips_fact(
                response_obj,
                FactGenType.RESP_FROM_WEB
            )
            self.env_provider.insert_fact(clips_fact)

        else:
            clips_fact = data_provider.generate_one_clips_fact(
                data_json_construct,
                FactGenType.PLAIN
            )
            self.env_provider.insert_fact(clips_fact)

        return 0

    def gen_clips_modify_fact_expr(self, var_name, args):
        clips_field_mods = []
        length = len(args)
        i = 0
        while i < length:
            field_name = args[i]
            field_value = args[i+1]
            field_type = args[i+2]

            if field_type == "STRING":
                field_value = '"' + field_value + '"'
            clips_field_mods.append("(" + \
                                    field_name + " " + \
                                    field_value + \
                                    ")")
            i += 3

        return "(modify " + var_name + " " + \
               " ".join(clips_field_mods) + \
               ")"

    def get_primary_key_field(self, data_provider):
        for field in data_provider.dsd.fields:
            if field.use_as == "\"primary-key\"":
                return field

    def get_field_value_from_args(self, args, field_name):
        length = len(args)
        i = 0
        while i < length:
            f_name = args[i]
            value = args[i+1]
            value_type = args[i+2]
            if f_name == field_name:
                new_value = value
                if value_type == "STRING":
                    new_value = '"' + new_value + '"'
                return new_value
            i += 3

    def update_func_call(self, *args):
        args = map(lambda arg: arg.replace('"', ''), args)
        args = list(args)

        self.print_args(args, "UPDATE")

        MODEL_ID_POS      = 0
        REFLECT_INFO_POS  = 2
        DATA_LEN_POS      = 4
        DATA_START_POS    = 5

        # Obtain data from args
        data_provider = self.data_providers_map[args[MODEL_ID_POS]]
        reflect_on_web = string_to_py_type(args[REFLECT_INFO_POS], "BOOLEAN")
        data_len = string_to_py_type(args[DATA_LEN_POS], "INTEGER")

        REF_LEN_POS = DATA_START_POS + data_len + 1
        REF_START_POS = REF_LEN_POS + 1

        ref_len = string_to_py_type(args[REF_LEN_POS], "INTEGER")

        # Build and deploy modification rule
        primary_key_field_name = \
            self.get_primary_key_field(data_provider).field_name

        if reflect_on_web:
            primary_key_field_value = self.get_field_value_from_args(
                args[REF_START_POS:REF_START_POS+ref_len],
                primary_key_field_name
            )
        else:
            primary_key_field_value = self.get_field_value_from_args(
                args[DATA_START_POS:DATA_START_POS+data_len],
                primary_key_field_name
            )
        if self.debug:
            print("PRIM KEY NAME: " + primary_key_field_name)
            print("PRIM KEY VALUE: " + str(primary_key_field_value))
            print("ENDD")
        rhs = """{{ "?to_update<-": "[{0}.{1} == {2}]" }}""".format(
                data_provider.dsd.model_id,
                str(primary_key_field_name),
                str(primary_key_field_value).replace('"', "'")
            )
        lhs = """{{ "clips": "{0}" }}""".format(
            self.gen_clips_modify_fact_expr(
                "?to_update",
                args[DATA_START_POS:DATA_START_POS+data_len]
            )
        )
        tmp_update_rule = GENERIC_RULE.format(
            "__update_fact_" + str(uuid.uuid4()).replace('-', ''),
            "\"system\"",
            "true",
            rhs,
            lhs
        )
        if self.debug:
            print("\nMODIFICATION FULE: " + tmp_update_rule)
            print("\nMOD. RULE TRANSPILATION PRINT:")
        transpiler = Transpiler(self.env_provider)

        try:
            transpiler.load(tmp_update_rule)
        except AkashicError as ae:
            print(ae.message)

        self.env_provider.insert_rule(transpiler.rule.rule_name,
                                      transpiler.tranpiled_rule)

        # Reflect modification on web if required
        if reflect_on_web:
            data_json_construct = self.data_arg_list_to_request_body(
                args[DATA_START_POS:DATA_START_POS+data_len]
            )

            url_map_args = self.ref_arg_list_to_url_map(
                *args[REF_START_POS:REF_START_POS+ref_len],
                data_provider.dsd.apis.update
            )

            response_obj = data_provider.update(data_json_construct,
                                                **url_map_args)
            if self.debug:
                print("\nMAP: " + str(url_map_args))
                print("\nUPDATE RESPONSE:\n" + str(response_obj) + "\n\n")

        # Print all facts
        if self.debug:
            print("FACTS - print from bridge:")
            for f in self.env_provider.env.facts():
                print("---------")
                print(f)

            print("****")
        return 0

    def return_func_call(self, *args):
        args = map(lambda arg: arg.replace('"', ''), args)
        args = list(args)

        self.print_args(args, "RETURN")

        TAG_POS = 0
        DATA_LEN_POS = 2
        DATA_START_POS = 3

        tag = string_to_py_type(args[TAG_POS], "STRING")
        data_len = string_to_py_type(args[DATA_LEN_POS], "INTEGER")
        data_json_construct = self.data_arg_list_to_request_body(
            args[DATA_START_POS : (DATA_START_POS + data_len)],
        )

        return_body = {}
        return_body["meta"] = {}
        return_body["meta"]["tag"] = tag
        return_body["data"] = data_json_construct

        self.env_provider.add_return_data(json.dumps(return_body, indent=4))
        return 0

    def delete_func_call(self, *args):
        args = map(lambda arg: arg.replace('"', ''), args)
        args = list(args)

        self.print_args(args, "DELETE")

        MODEL_ID_POS     = 0
        REFLECT_INFO_POS = 2
        REF_LEN_POS      = 4
        REF_START_POS    = 5

        # Obtain data from args
        data_provider = self.data_providers_map[args[MODEL_ID_POS]]
        reflect_on_web = string_to_py_type(args[REFLECT_INFO_POS], "BOOLEAN")
        ref_len = string_to_py_type(args[REF_LEN_POS], "INTEGER")

        # Build and deploy modification rule
        primary_key_field_name = \
            self.get_primary_key_field(data_provider).field_name
        primary_key_field_value = self.get_field_value_from_args(
            args[REF_START_POS:REF_START_POS+ref_len],
            primary_key_field_name
        )
        rhs = """{{ "?to_delete<-": "[{0}.{1} == {2}]" }}""".format(
                data_provider.dsd.model_id,
                str(primary_key_field_name),
                str(primary_key_field_value).replace('"', "'")
            )
        lhs = """{{ "clips": "{0}" }}""".format(
            "(retract ?to_delete)"
        )
        tmp_delete_rule = GENERIC_RULE.format(
            "__delete_fact_" + str(uuid.uuid4()).replace('-', ''),
            "\"system\"",
            "true",
            rhs,
            lhs
        )
        if self.debug:
            print("\nDELETION RULE: " + tmp_delete_rule)
            print("\nDEL. RULE TRANSPILATION PRINT:")
        transpiler = Transpiler(self.env_provider)
        try:
            transpiler.load(tmp_delete_rule)
        except AkashicError as ae:
            print(ae.message)

        self.env_provider.insert_rule(transpiler.rule.rule_name,
                                      transpiler.tranpiled_rule)

        # Reflect modification on web if required
        if reflect_on_web:
            url_map_args = self.ref_arg_list_to_url_map(
                args[REF_START_POS:REF_START_POS+ref_len],
                data_provider.dsd.apis.update
            )

            response_obj = data_provider.delete(**url_map_args)

            if self.debug:
                print("\nMAP: " + str(url_map_args))
                print("\nDELETION RESPONSE:\n" + str(response_obj) + "\n\n")

        # Print all facts
        if self.debug:
            print("FACTS - print from bridge:")
            for f in self.env_provider.env.facts():
                print("---------")
                print(f)

            print("****")
        return 0

    def process_query_call(self, *args):
        args = map(lambda arg: arg.replace('"', ''), args)
        args = list(args)

        self.print_args(args, "PROCESS_QUERY")

        unique_rule_name = string_to_py_type(args[0], "STRING")
        template_name = string_to_py_type(args[1], "STRING")
        field_name = string_to_py_type(args[2], "STRING")

        line_start = string_to_py_type(args[3], "INTEGER")
        col_start  = string_to_py_type(args[4], "INTEGER")
        line_end   = string_to_py_type(args[5], "INTEGER")
        col_end    = string_to_py_type(args[6], "INTEGER")

        tmp_update_rule = QUERY_RULE.format(
            unique_rule_name,
            template_name,
            field_name,
            line_start,
            col_start,
            line_end,
            col_end
        )

        if self.debug:
            print("\nQUERY RULE: " + tmp_update_rule)
            print("\nQUERY RULE TRANSPILATION PRINT:")
        transpiler = Transpiler(self.env_provider, True)
        transpiler.load(tmp_update_rule)

        self.env_provider.insert_rule(transpiler.rule.rule_name,
                                      transpiler.tranpiled_rule)

        return 0


from pprint import pprint
import re

instruction_re = re.compile('([\w]+) to ([\w]+) = ([0-9]+)')

city_graph = {}

with open('input.txt', 'r') as f:
    for line in f.readlines():
        match = instruction_re.match(line.strip())
        start, stop, distance = match.groups()
        if start in city_graph:
            city_graph[start].append((stop, int(distance)))
        else:
            city_graph[start] = [(stop, int(distance))]
        if stop in city_graph:
            city_graph[stop].append((start, int(distance)))
        else:
            city_graph[stop] = [(start, int(distance))]

cities = city_graph.keys()

def find_paths(graph, start, end, path=[]):
    path = path + [start]
    if start == end:
        return [path]
    if start not in graph:
        return []
    paths = []
    for node, distance in graph[start]:
        if node not in path:
            newpaths = find_paths(graph, node, end, path)
            for newpath in newpaths:
                paths.append(newpath)
    return paths

good_paths = []

for start_city in cities:
    for end_city in cities:
        if start_city == end_city:
            continue
        paths = find_paths(city_graph, start_city, end_city)
        for path in paths:
            if len(path) == len(cities):
                distance = 0
                for i in range(len(path) - 1):
                    s, e = path[i], path[i+1]
                    for dest, dist in city_graph[s]:
                        if dest == e:
                            distance += dist
                good_paths.append((path, distance))

sorted_paths = sorted(good_paths, key=lambda p: p[1])

print('shortest: {0}'.format(sorted_paths[0][1]))
print('longest:  {0}'.format(sorted_paths[-1][1]))


"""
Tornado handler to edit/delete a log upload entry
"""
from __future__ import print_function
import os
from html import escape
import sqlite3
import sys
import tornado.web

# this is needed for the following imports
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), '../plot_app'))
from config import get_db_filename, get_kml_filepath, get_overview_img_filepath
from helper import clear_ulog_cache, get_log_filename

#pylint: disable=relative-beyond-top-level
from .common import get_jinja_env

EDIT_TEMPLATE = 'edit.html'

#pylint: disable=abstract-method

class EditEntryHandler(tornado.web.RequestHandler):
    """ Edit a log entry, with confirmation (currently only delete) """

    def get(self, *args, **kwargs):
        """ GET request """
        log_id = escape(self.get_argument('log'))
        action = self.get_argument('action')
        confirmed = self.get_argument('confirm', default='0')
        token = escape(self.get_argument('token'))

        if action == 'delete':
            if confirmed == '1':
                if self.delete_log_entry(log_id, token):
                    content = """

Log File deleted

Successfully deleted the log file.

"""
                else:
                    content = """

Failed

Failed to delete the log file.

"""
            else: # request user to confirm
                # use the same url, just append 'confirm=1'
                delete_url = self.request.path+'?action=delete&log='+log_id+ \
                        '&token='+token+'&confirm=1'
                content = """

Delete Log File

Click here to confirm and delete the log {log_id}.

""".format(delete_url=delete_url, log_id=log_id)
        else:
            raise tornado.web.HTTPError(400, 'Invalid Parameter')

        template = get_jinja_env().get_template(EDIT_TEMPLATE)
        self.write(template.render(content=content))

    @staticmethod
    def delete_log_entry(log_id, token):
        """
        delete a log entry (DB & file), validate token first

        :return: True on success
        """
        con = sqlite3.connect(get_db_filename(), detect_types=sqlite3.PARSE_DECLTYPES)
        cur = con.cursor()
        cur.execute('select Token from Logs where Id = ?', (log_id,))
        db_tuple = cur.fetchone()
        if db_tuple is None:
            return False
        if token != db_tuple[0]: # validate token
            return False

        # kml file
        kml_path = get_kml_filepath()
        kml_file_name = os.path.join(kml_path, log_id.replace('/', '.')+'.kml')
        if os.path.exists(kml_file_name):
            os.unlink(kml_file_name)

        #preview image
        preview_image_filename = os.path.join(get_overview_img_filepath(), log_id+'.png')
        if os.path.exists(preview_image_filename):
            os.unlink(preview_image_filename)

        log_file_name = get_log_filename(log_id)
        print('deleting log entry {} and file {}'.format(log_id, log_file_name))
        os.unlink(log_file_name)
        cur.execute("DELETE FROM LogsGenerated WHERE Id = ?", (log_id,))
        cur.execute("DELETE FROM Logs WHERE Id = ?", (log_id,))
        con.commit()
        cur.close()
        con.close()

        # need to clear the cache as well
        clear_ulog_cache()

        return True


# coding=utf-8
import math
import functools

# 场景
class Scene(object):

	def __init__(self):
		self.__infos = [] # 缓冲的行情(KLine)
		self.__ind = {} # 绑定的指标(name : BaseInd)
		self.__strategy = [] # 绑定的策略([prio(int), impl])

	def update(self, new_info):

		# 更新行情
		self.__infos.append(new_info)

		# 更新绑定的指标
		for k,v in self.__ind.iteritems():
			v.update(self)

		# 更新策略
		for i in range(len(self.__strategy)):
			strategy = self.__strategy[i]
			strategy[1](self, strategy[2])

	def addIndicator(self, name, new_ind):
		self.__ind[name] = new_ind

	def bindStrategy(self, prio=0, ctx=None):
		def wrapper_arg(func):
			@functools.wraps(func)
			def wrapper(*args, **kwargs):
				result = func(*args, **kwargs)
				return result
			new_item = [prio, wrapper, ctx]
			self.__strategy.append(new_item)
			sorted(self.__strategy, key=lambda item:item[0])
			return wrapper
		return wrapper_arg

	def indicator(self, name):
		return self.__ind[name]

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

	def __getitem__(self, key):
		if not isinstance(key, int):
			raise KeyError
		return self.__infos[key]

	@property
	def last(self):
		if len(self)>0:
			return self[-1]
		return None

	def __str__(self):
		ret = ''
		line = '\r\n'

		ret += '行情数据:'+line
		for i in range(len(self)):
			ret += str(self[i])+line
		ret += line

		for k,v in self.__ind.iteritems():
			ret += '指标%s数据:'%(k)+line
			ret += str(v)
			ret += line

		return ret

# 指标接口
class BaseInd(object):

	def __init__(self):
		pass

	def update(self, scene):
		pass

# MA指标(Moving Averagr)
class MA(BaseInd):
	def __init__(self, interval=10):
		super(MA, self).__init__()
		self.__interval = interval
		self.__infos = [] # [0:时间,1:ma数据]

	@property
	def interval(self):
		return self.__interval

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

	def __getitem__(self, key):
		if not isinstance(key, int):
			raise KeyError
		return self.__infos[key]

	@property
	def last(self):
		if len(self.__infos)<=0:
			return None
		item = self[-1]
		return (item[0], item[1])

	def update(self, scene):
		if len(scene)<self.interval:
			return
		sum = 0.0
		for i in range(self.interval):
			sum += scene[-i-1].close
		sum /= float(self.interval)
		new_item = [scene[-1].time, sum]
		self.__infos.append(new_item)

	def __str__(self):
		ret = ''
		for i in range(len(self)):
			item = self[i]
			ret += '%s %.2f\r\n' % (str(item[0]), item[1])
		return ret

# DM指标
class DM(BaseInd):
	'''
	DM : 方向性移动运动指标(Directional Move Indicator),分为"正方向移动运动指标(DM+)"与"负方向移动运动指标(DM-)".
	最高价变化 : 当前最高价-先前最高价;
	最低价变化 : 先前最低价-当前最低价.

	若"最高价/最低价变化都<0"或“最高价/最低价变化相等”,则DM+与DM-都为0;
	若"最高价变化>最低价变化",则DM+为最高价变化,DM-为0;
	若"最高价变化<最低价变化",则DM+为0,DM-为最低价变化.
	'''
	def __init__(self):
		super(DM, self).__init__()
		self.__infos = [] # [0:时间,1:DM+数据,2:DM-数据]

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

	def __getitem__(self, key):
		if not isinstance(key, int):
			raise KeyError
		return self.__infos[key]

	@property
	def last(self):
		if len(self.__infos)<=0:
			return None
		item = self[-1]
		return (item[0], item[1], item[2])

	def update(self, scene):
		if len(scene)<2:
			return

		actual = -1
		prev = -2
		high_var = scene[actual].high-scene[prev].high
		low_var = scene[prev].low-scene[actual].low
		if high_var==low_var or \
			(high_var<0.0 and low_var<0.0):
			dm_plus = 0.0
			dm_minus = 0.0
		elif high_var>low_var:
			dm_plus = high_var
			dm_minus = 0.0
		else:
			dm_plus = 0.0
			dm_minus = low_var
		new_item = [scene[actual].time, dm_plus, dm_minus]
		self.__infos.append(new_item)

	def __str__(self):
		ret = ''
		for i in range(len(self)):
			item = self[i]
			ret += '%s %.2f %.2f\r\n' % (str(item[0]), item[1], item[2])
		return ret

# ADM指标
class ADM(BaseInd):
	'''
	指标DM的滑动平均
	'''
	def __init__(self, interval=10):
		super(ADM, self).__init__()
		self.__internal_dm = DM()
		self.__interval = interval
		self.__infos = [] # [0:时间,1:ADM+数据,2:ADM-数据]

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

	def __getitem__(self, key):
		if not isinstance(key, int):
			raise KeyError
		return self.__infos[key]

	@property
	def interval(self):
		return self.__interval

	@property
	def last(self):
		if len(self.__infos)<=0:
			return None
		item = self[-1]
		return (item[0], item[1], item[2])

	def update(self, scene):
		self.__internal_dm.update(scene)

		if len(self.__internal_dm)<self.interval:
			return

		prev = -2
		actual = -1

		result_plus = 0.0
		result_minus=  0.0
		if len(self)==0:
			# ADM+/ADM-第一次计算,取先前N个DM+/DM-的算术平均
			for i in range(self.interval):
				dm = self.__internal_dm[-i-1]
				result_plus += dm[1]
				result_minus += dm[2]
		else:
			result_plus += self.last[1]*(self.interval-1)+self.__internal_dm[actual][1]
			result_minus += self.last[2]*(self.interval-1)+self.__internal_dm[actual][2]
		result_plus /= self.interval
		result_minus /= self.interval

		new_item = [scene[actual].time, result_plus, result_minus]
		self.__infos.append(new_item)

	def __str__(self):
		ret = ''
		for i in range(len(self)):
			item = self[i]
			ret += '%s %.2f %.2f\r\n' % (str(item[0]), item[1], item[2])
		return ret

# TR指标
class TR(BaseInd):

	def __init__(self):
		super(TR, self).__init__()
		self.__infos = [] # [0:时间,1:TR数据]

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

	def __getitem__(self, key):
		if not isinstance(key, int):
			raise KeyError
		return self.__infos[key]

	@property
	def last(self):
		if len(self.__infos)<=0:
			return None
		item = self[-1]
		return (item[0], item[1])

	def update(self, scene):
		if len(scene)<2:
			return

		actual = -1
		prev =  -2
		val = math.fabs(scene[actual].high-scene[actual].low)
		val = max(val, math.fabs(scene[actual].high-scene[prev].close))
		val = max(val, math.fabs(scene[actual].low-scene[prev].close))
		new_item = [scene[actual].time, val]
		self.__infos.append(new_item)

	def __str__(self):
		ret = ''
		for i in range(len(self)):
			item = self[i]
			ret += '%s %.2f\r\n' % (str(item[0]), item[1])
		return ret

# ATR指标
class ATR(BaseInd):
	'''
	指标TR的滑动平均
	'''
	def __init__(self, interval=10):
		super(ATR, self).__init__()
		self.__internal_tr = TR()
		self.__interval = interval
		self.__infos = [] # [0:时间,1:ATR数据]

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

	def __getitem__(self, key):
		if not isinstance(key, int):
			raise KeyError
		return self.__infos[key]

	@property
	def interval(self):
		return self.__interval

	@property
	def last(self):
		if len(self.__infos)<=0:
			return None
		item = self[-1]
		return (item[0], item[1])

	def update(self, scene):
		self.__internal_tr.update(scene)

		if len(self.__internal_tr)<self.interval:
			return

		prev = -2
		actual = -1
		result = 0.0
		if len(self)==0:
			# ATR第一次计算,取先前N个TR的算术平均
			for i in range(self.interval):
				tr = self.__internal_tr[-i-1]
				result += tr[1]
		else:
			result += self.last[1]*(self.interval-1)+self.__internal_tr[actual][1]
		result /= self.interval

		new_item = [scene[actual].time, result]
		self.__infos.append(new_item)

	def __str__(self):
		ret = ''
		for i in range(len(self)):
			item = self[i]
			ret += '%s %.2f\r\n' % (str(item[0]), item[1])
		return ret

# DX指标
class DX(BaseInd):

	def __init__(self, interval=10):
		super(DX, self).__init__()
		self.__interval = interval
		self.__internal_adm = ADM(self.interval)
		self.__internal_atr = ATR(self.interval)
		self.__infos = [] # [0:时间,1:DX数据]

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

	def __getitem__(self, key):
		if not isinstance(key, int):
			raise KeyError
		return self.__infos[key]

	@property
	def interval(self):
		return self.__interval

	@property
	def last(self):
		if len(self.__infos)<=0:
			return None
		item = self[-1]
		return (item[0], item[1])

	def update(self, scene):
		self.__internal_adm.update(scene)
		self.__internal_atr.update(scene)

		if len(self.__internal_adm)<=0 or len(self.__internal_atr)<=0:
			return

		adm_plus = self.__internal_adm.last[1]
		adm_minus = self.__internal_adm.last[2]
		atr = self.__internal_atr.last[1]

		di_plus = round(adm_plus/atr*100.0)
		di_minus = round(adm_minus/atr*100.0)
		dx = round(math.fabs(di_plus-di_minus)/(di_plus+di_minus)*100.0)

		new_item = [scene.last.time, dx]
		self.__infos.append(new_item)

	def __str__(self):
		ret = ''
		for i in range(len(self)):
			item = self[i]
			ret += '%s %.2f\r\n' % (str(item[0]), item[1])
		return ret

# ADX指标
class ADX(BaseInd):
	'''
	ADX指标 : DX指标的滑动平均.
	用于衡量当前趋势的强弱,若大于一定值(一般40),则认为处于趋势中,若小于一定值(一般20),则认为处于盘整中.
	ADX的窗口宽度多取14. ---> 14代表"两周/半月",可尝试取"10/11".
	'''
	def __init__(self, interval=10):
		super(ADX, self).__init__()
		self.__interval = interval
		self.__internal_dx = DX(self.interval)
		self.__infos = [] # [0:时间,1:adx数据]

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

	def __getitem__(self, key):
		if not isinstance(key, int):
			raise KeyError
		return self.__infos[key]

	@property
	def interval(self):
		return self.__interval

	@property
	def last(self):
		if len(self.__infos)<=0:
			return None
		item = self[-1]
		return (item[0], item[1])

	def update(self, scene):
		self.__internal_dx.update(scene)

		if len(self.__internal_dx)<self.interval:
			return

		result = 0.0
		if len(self)==0:
			# ADX第一次计算,取先前N个DX的算术平均
			for i in range(self.interval):
				dx = self.__internal_dx[-i-1]
				result += dx[1]
		else:
			result += self.last[1]*(self.interval-1)+self.__internal_dx.last[1]
		result /= self.interval

		new_item = [scene.last.time, result]
		self.__infos.append(new_item)

	def __str__(self):
		ret = ''
		for i in range(len(self)):
			item = self[i]
			ret += '%s %.2f\r\n' % (str(item[0]), item[1])
		return ret


"""
Author: CHR-onicles©
Date: 05/12/2020

DSC UG Python Challenge - Time Tracker App.
"""

from tkinter import *
from tkcalendar import *
from tkinter.filedialog import askdirectory
from tkinter import messagebox
from PIL import Image, ImageTk
from datetime import datetime as dt
import openpyxl
import os
import threading

# MAIN WINDOW CONFIGURATIONS

root = Tk()
root.title('TimeTracker')
root.iconbitmap('images/cat.ico')

SCREEN_HEIGHT = root.winfo_screenheight()
SCREEN_WIDTH = root.winfo_screenwidth()

# print(SCREEN_HEIGHT, SCREEN_WIDTH)  # For debugging
ROOT_WIDTH = 550
ROOT_HEIGHT = 640

# x and y coordinates to center app on screen
x_pos = (SCREEN_WIDTH/2) - (ROOT_WIDTH/2)
y_pos = (SCREEN_HEIGHT/2) - (ROOT_HEIGHT/2)

root.geometry(f'{ROOT_WIDTH}x{ROOT_HEIGHT}+{int(x_pos)}+{int(y_pos)}')

# Preventing user from increasing or reducing app size as most widgets are statically placed!
root.resizable(False, False)
root.configure(bg='#5fc29e')  # turquoise-ish colour used in status bar and calendar.

# GLOBAL VARIABLES------------

# Calendar stuff
cal_window = None
first_time_open = False
date_from_calendar = ''
date_from_calendar2 = ''

# Status bar text
rate_text = 'Rate: 1hr = $5.00'

# Payment calculation variable
amount_to_be_paid = 0.0
total_hours_spent = 0.0

# Excel stuff variable
excel_first_time_open = 0
alpha_current_dir = os.getcwd()
dir_location = ''
# TODO: Put coordinates of buttons in global variables for easy update of button locations.
# -----------------------------

# DEFINING BINDING EVENT FUNCTIONS

def spin_box_hover_in(event, spin_box):
    """
    Function to change the spinbox's foreground colour and status bar when hovered on.

    :param event: necessary argument in order for binding event to work.
    :param spin_box: spin box object that is passed in to be updated.
    """
    spin_box.config(fg='black')

    if spin_box == top_hour_spin_box or spin_box == m_hour_spin_box:
        status_bar_left.config(text='Set the hour...', font=('consolas', 12, 'italic'))

    elif spin_box == top_minute_spin_box or spin_box == m_minute_spin_box:
        status_bar_left.config(text='Set the minutes...', font=('consolas', 12, 'italic'))

def spin_box_hover_out(event, spin_box):
    """
    Function to change the spinbox's foreground color back to normal.

    :param event: necessary argument in order for binding event to work.
    :param spin_box: spin box object that is passed in to be updated.
    """
    spin_box.config(fg='gray')
    status_bar_left.config(text=rate_text, font=('consolas', 12))

def submit_button_hover_in(event):
    """
    Binding event function to update submit button in calendar,
    when it is hovered on.
    """
    submit_button.config(bg='white', font=('consolas', 14, 'italic'))

def submit_button_hover_out(event):
    """
    Binding event function to update submit button in calendar,
    when it is hovered out of.
    """
    submit_button.config(bg='SystemButtonFace', font=('consolas', 14))

def calculate_payment_button_hover_in(event):
    """
    Function to change colour and font style of calculate payment button and status bar
    when hovered on.
    """
    calc_payment_button.config(font=('consolas', 22))
    status_bar_left.config(text='Calculate payment based on info provided...', font=('consolas', 10, 'italic'))

def calculate_payment_button_hover_out(event):
    """
    Function to change calculate payment button's colour, font style and status bar
    back to normal when mouse pointer is no longer hovering on the button.
    """
    calc_payment_button.config(font=('consolas', 20), bg='SystemButtonFace')
    status_bar_left.config(text=rate_text, font=('consolas', 12))

def save_button_hover_in(event):
    """
    Binding event function to update save button's color, size and status bar
    when mouse pointer hovers on it.
    """
    global save_icon, save_button, sb_window
    bottom_canvas.delete(sb_window)
    save_icon = ImageTk.PhotoImage(Image.open('images/save.png').resize((70, 65), Image.ANTIALIAS))
    save_button = Button(bottom_canvas, image=save_icon, command=save_to_excel)
    sb_window = bottom_canvas.create_window(490, 160, window=save_button)
    save_button.bind('<Leave>', save_button_hover_out)

    # Update status bar
    status_bar_left.config(text='Save payment info to excel file...', font=('consolas', 11, 'italic'))

def save_button_hover_out(event):
    """
    Binding event function to return save button to normal when hovered out of.
    """
    global save_icon, save_button, sb_window
    bottom_canvas.delete(sb_window)
    save_icon = ImageTk.PhotoImage(Image.open('images/save.png').resize((65, 60), Image.ANTIALIAS))
    save_button = Button(bottom_canvas, image=save_icon, command=save_to_excel)
    sb_window = bottom_canvas.create_window(490, 160, window=save_button)
    save_button.bind('<Enter>', save_button_hover_in)

    # Update status bar again
    status_bar_left.config(text=rate_text, font=('consolas', 12))

def reset_button_hover_in(event):
    """
    Function to update reset button and status bar when hovered on.
    """
    global reset_icon, reset_button, r_window
    bottom_canvas.delete(r_window)
    reset_icon = ImageTk.PhotoImage(Image.open('images/Reset.png').resize((65, 65), Image.ANTIALIAS))
    reset_button = Button(bottom_canvas, image=reset_icon, command=reset_values)
    r_window = bottom_canvas.create_window(60, 160, window=reset_button)
    reset_button.bind('<Leave>', reset_button_hover_out)

    # Update status bar
    status_bar_left.config(text='Reset calculation...', font=('consolas', 12, 'italic'))

def reset_button_hover_out(event):
    """
    Function to update reset button and status bar back to normal when hovered out of.
    """
    global reset_icon, reset_button, r_window
    bottom_canvas.delete(r_window)
    reset_icon = ImageTk.PhotoImage(Image.open('images/Reset.png').resize((60, 60), Image.ANTIALIAS))
    reset_button = Button(bottom_canvas, image=reset_icon, command=reset_values)
    r_window = bottom_canvas.create_window(60, 160, window=reset_button)
    reset_button.bind('<Enter>', reset_button_hover_in)

    # Update status bar
    status_bar_left.config(text=rate_text, font=('consolas', 12))

def calendar_button_hover_in(event, canvas):
    """
    Function to update the calendar button when it is hovered on.
    """

    # Created 2 separate instances of the hover event because of conflict between them when they both use the same
    # resources.
    if canvas == top_canvas:  # this is top canvas
        global cl_window, calendar_button, cal_icon
        canvas.delete(cl_window)
        cal_icon = ImageTk.PhotoImage(Image.open('images/cal2_icon.png').resize((50, 50), Image.ANTIALIAS))
        calendar_button = Button(canvas, image=cal_icon, bg='light gray', command=lambda: open_calendar(canvas))
        cl_window = canvas.create_window(136, 100, window=calendar_button)
        calendar_button.bind('<Leave>', lambda e: calendar_button_hover_out(e, canvas))

    else:
        global cl_window2, cal_icon2, calendar_button2
        canvas.delete(cl_window2)
        cal_icon2 = ImageTk.PhotoImage(Image.open('images/cal2_icon.png').resize((50, 50), Image.ANTIALIAS))
        calendar_button2 = Button(canvas, image=cal_icon2, bg='light gray', command=lambda: open_calendar(canvas))
        cl_window2 = canvas.create_window(136, 100, window=calendar_button2)
        calendar_button2.bind('<Leave>', lambda e: calendar_button_hover_out(e, canvas))

    status_bar_left.config(text='Pick a date from the calendar...', font=('consolas', 12, 'italic'))

def calendar_button_hover_out(event, canvas):
    """
    Function to update the calendar button when it is hovered out of.
    """
    if canvas == top_canvas:  # top canvas
        global cl_window, calendar_button, cal_icon
        canvas.delete(cl_window)
        cal_icon = ImageTk.PhotoImage(Image.open('images/cal2_icon.png').resize((45, 45), Image.ANTIALIAS))
        calendar_button = Button(canvas, image=cal_icon, bg='light gray', command=lambda: open_calendar(canvas))
        cl_window = canvas.create_window(136, 100, window=calendar_button)
        calendar_button.bind('<Enter>', lambda e: calendar_button_hover_in(e, canvas))

    else:  # middle canvas
        global cl_window2, cal_icon2, calendar_button2
        canvas.delete(cl_window2)
        cal_icon2 = ImageTk.PhotoImage(Image.open('images/cal2_icon.png').resize((45, 45), Image.ANTIALIAS))
        calendar_button2 = Button(canvas, image=cal_icon2, bg='light gray', command=lambda: open_calendar(canvas))
        cl_window2 = canvas.create_window(136, 100, window=calendar_button2)
        calendar_button2.bind('<Enter>', lambda e: calendar_button_hover_in(e, canvas))

    status_bar_left.config(text=rate_text, font=('consolas', 12))

def confirm_button_hover_in(event, canvas):
    """
    Function to update the confirm button when it is hovered on.
    """
    if canvas == top_canvas:
        global c_icon, check_button, cf_window
        canvas.delete(cf_window)
        c_icon = ImageTk.PhotoImage(Image.open('images/check_icon.png').resize((65, 65), Image.ANTIALIAS))
        check_button = Button(canvas, image=c_icon, command=lambda: confirm_button_click(canvas))
        cf_window = canvas.create_window(490, 90, window=check_button)
        check_button.bind('<Leave>', lambda e: confirm_button_hover_out(e, canvas))

    else:  # middle canvas
        global c_icon2, check_button2, cf_window2
        canvas.delete(cf_window2)
        c_icon2 = ImageTk.PhotoImage(Image.open('images/check_icon.png').resize((65, 65), Image.ANTIALIAS))
        check_button2 = Button(canvas, image=c_icon2, command=lambda: confirm_button_click(canvas))
        cf_window2 = canvas.create_window(490, 90, window=check_button2)
        check_button2.bind('<Leave>', lambda e: confirm_button_hover_out(e, canvas))

    status_bar_left.config(text='Confirm date and time selection...', font=('consolas', 11, 'italic'))

def confirm_button_hover_out(event, canvas):
    """
    Function to update the confirm button when it is hovered out of.
    """
    if canvas == top_canvas:  # top canvas
        global c_icon, check_button, cf_window
        canvas.delete(cf_window)
        c_icon = ImageTk.PhotoImage(Image.open('images/check_icon.png').resize((60, 60), Image.ANTIALIAS))
        check_button = Button(canvas, image=c_icon, command=lambda: confirm_button_click(canvas))
        cf_window = canvas.create_window(490, 90, window=check_button)
        check_button.bind('<Enter>', lambda e: confirm_button_hover_in(e, canvas))

    else:  # middle canvas
        global c_icon2, check_button2, cf_window2
        canvas.delete(cf_window2)
        c_icon2 = ImageTk.PhotoImage(Image.open('images/check_icon.png').resize((60, 60), Image.ANTIALIAS))
        check_button2 = Button(canvas, image=c_icon2, command=lambda: confirm_button_click(canvas))
        cf_window2 = canvas.create_window(490, 90, window=check_button2)
        check_button2.bind('<Enter>', lambda e: confirm_button_hover_in(e, canvas))

    status_bar_left.config(text=rate_text, font=('consolas', 12))

def cancel_button_hover_in(event, canvas):
    """
    Function to update the cancel button when it is hovered on.
    """
    if canvas == top_canvas:
        global cancel_icon, cancel_button, cx_window
        canvas.delete(cx_window)
        cancel_icon = ImageTk.PhotoImage(Image.open('images/remove_icon.jpg').resize((65, 65), Image.ANTIALIAS))
        cancel_button = Button(canvas, image=cancel_icon, command=lambda: cancel_button_click(canvas))
        cx_window = canvas.create_window(490, 160, window=cancel_button)
        cancel_button.bind('<Leave>', lambda e: cancel_button_hover_out(e, canvas))

    else:  # middle canvas
        global cancel_icon2, cancel_button2, cx_window2
        canvas.delete(cx_window2)
        cancel_icon2 = ImageTk.PhotoImage(Image.open('images/remove_icon.jpg').resize((65, 65), Image.ANTIALIAS))
        cancel_button2 = Button(canvas, image=cancel_icon2, command=lambda: cancel_button_click(canvas))
        cx_window2 = canvas.create_window(490, 160, window=cancel_button2)
        cancel_button2.bind('<Leave>', lambda e: cancel_button_hover_out(e, canvas))

    status_bar_left.config(text='Cancel date and time selected...', font=('consolas', 11, 'italic'))

def cancel_button_hover_out(event, canvas):
    """
    Function to update the cancel button when it is hovered out of.
    """
    if canvas == top_canvas:
        global cancel_icon, cancel_button, cx_window
        canvas.delete(cx_window)
        cancel_icon = ImageTk.PhotoImage(Image.open('images/remove_icon.jpg').resize((60, 60), Image.ANTIALIAS))
        cancel_button = Button(canvas, image=cancel_icon, command=lambda: cancel_button_click(canvas))
        cx_window = canvas.create_window(490, 160, window=cancel_button)
        cancel_button.bind('<Enter>', lambda e: cancel_button_hover_in(e, canvas))

    else:  # middle canvas
        global cancel_icon2, cancel_button2, cx_window2
        canvas.delete(cx_window2)
        cancel_icon2 = ImageTk.PhotoImage(Image.open('images/remove_icon.jpg').resize((60, 60), Image.ANTIALIAS))
        cancel_button2 = Button(canvas, image=cancel_icon2, command=lambda: cancel_button_click(canvas))
        cx_window2 = canvas.create_window(490, 160, window=cancel_button2)
        cancel_button2.bind('<Enter>', lambda e: cancel_button_hover_in(e, canvas))

    status_bar_left.config(text=rate_text, font=('consolas', 12))

def time_now_button_hover_in(event, canvas):
    """
    Function to update time_now button when it is hovered on.
    """
    if canvas == top_canvas:
        global time_now_icon, time_now_button, tn_window
        canvas.delete(tn_window)
        time_now_icon = ImageTk.PhotoImage(Image.open('images/t_now.png').resize((50, 50), Image.ANTIALIAS))
        time_now_button = Button(canvas, image=time_now_icon, command=lambda: get_current_date_time(canvas))
        tn_window = canvas.create_window(415, 100, window=time_now_button)
        time_now_button.bind('<Leave>', lambda e: time_now_button_hover_out(e, canvas))

    else:  # middle canvas
        global time_now_icon2, time_now_button2, tn_window2
        canvas.delete(tn_window2)
        time_now_icon2 = ImageTk.PhotoImage(Image.open('images/t_now.png').resize((50, 50), Image.ANTIALIAS))
        time_now_button2 = Button(canvas, image=time_now_icon2,
                                  command=lambda: get_current_date_time(canvas))
        tn_window2 = canvas.create_window(415, 100, window=time_now_button2)
        time_now_button2.bind('<Leave>', lambda e: time_now_button_hover_out(e, canvas))

    status_bar_left.config(text='Get current date and time...', font=('consolas', 12, 'italic'))

def time_now_button_hover_out(event, canvas):
    """
    Function to update time now button when it is hovered out of.
    """
    if canvas == top_canvas:
        global time_now_icon, time_now_button, tn_window
        canvas.delete(tn_window)
        time_now_icon = ImageTk.PhotoImage(Image.open('images/t_now.png').resize((45, 45), Image.ANTIALIAS))
        time_now_button = Button(canvas, image=time_now_icon, command=lambda: get_current_date_time(canvas))
        tn_window = canvas.create_window(415, 100, window=time_now_button)
        time_now_button.bind('<Enter>', lambda e: time_now_button_hover_in(e, canvas))

    else:  # middle canvas
        global time_now_icon2, time_now_button2, tn_window2
        canvas.delete(tn_window2)
        time_now_icon2 = ImageTk.PhotoImage(Image.open('images/t_now.png').resize((45, 45), Image.ANTIALIAS))
        time_now_button2 = Button(canvas, image=time_now_icon2,
                                  command=lambda: get_current_date_time(canvas))
        tn_window2 = canvas.create_window(415, 100, window=time_now_button2)
        time_now_button2.bind('<Enter>', lambda e: time_now_button_hover_in(e, canvas))

    status_bar_left.config(text=rate_text, font=('consolas', 12))

# DEFINING COMMAND FUNCTIONS--------------------------------------------------------

def open_calendar(canvas):
    """
    Command function to open calendar for user to choose a date.

    :param: canvas: canvas object for the function to know which canvas called it and
                    give it specific attributes.
    """

    global first_time_open, cal_window

    def create_calendar_window():
        global first_time_open, cal_window

        # Grabbing current date from system
        year = dt.now().year
        month = dt.now().month
        day = dt.now().day

        # Calendar window configurations
        cal_window = Toplevel()
        cal_window.title('Calendar')
        cal_window.iconbitmap('images/cat.ico')

        # To keep the calendar window within the app screen (specifically top left of app)
        cal_window.geometry(f'250x270+{int(x_pos)}+{int(y_pos)}')
        cal_window.resizable(False, False)
        cal_window.configure(bg='#5fc29e')

        def get_date():
            """
            Based on the canvas argument from outer function, this function
            updates a global variable on the date got from the calendar.
            """
            global cal_window, selected_label, date_from_calendar, date_from_calendar2
            if canvas == top_canvas:
                date_from_calendar = cal.get_date()

                # rearranging of date format to: DD/MM/YY
                temp = date_from_calendar.split('/')

                # Prepending 0 to single digits
                if len(temp[0]) == 1 and len(temp[1]) == 1:
                    temp[0] = '0' + temp[0]
                    temp[1] = '0' + temp[1]
                elif len(temp[0]) == 1:
                    temp[0] = '0' + temp[0]
                elif len(temp[1]) == 1:
                    temp[1] = '0' + temp[1]

                temp2 = temp[1] + '/' + temp[0] + '/' + temp[2]
                date_from_calendar = temp2

            elif canvas == middle_canvas:
                date_from_calendar2 = cal.get_date()

                # rearranging of date format to: DD/MM/YY
                temp = date_from_calendar2.split('/')

                # Prepending 0 to single digits
                if len(temp[0]) == 1 and len(temp[1]) == 1:
                    temp[0] = '0' + temp[0]
                    temp[1] = '0' + temp[1]
                elif len(temp[0]) == 1:
                    temp[0] = '0' + temp[0]
                elif len(temp[1]) == 1:
                    temp[1] = '0' + temp[1]

                temp2 = temp[1] + '/' + temp[0] + '/' + temp[2]
                date_from_calendar2 = temp2

            selected_label = Label(cal_window, text='Date Selected!', font=('consolas', 14, 'italic'),
                                   bg='#5fc29e')
            selected_label.pack(pady=5)
            submit_button.config(state=DISABLED)

        global cal
        cal = Calendar(cal_window, selectmode='day', year=year, month=month, day=day)
        cal.pack(fill=BOTH, expand=1)

        # Submit button for calendar
        global submit_button
        submit_button = Button(cal_window, text='Submit', font=('consolas', 14), command=get_date)
        submit_button.pack(pady=(5, 0))
        submit_button.bind('<Enter>', submit_button_hover_in)
        submit_button.bind('<Leave>', submit_button_hover_out)
        first_time_open = True

    if first_time_open is False:  # Opened calendar window for the first time
        create_calendar_window()

    else:
        if cal_window.winfo_exists():  # Calendar window is still open
            first_time_open = True
        else:  # Calendar window was opened but closed.
            create_calendar_window()

def status_bar_time_update():
    """
    Function to update the time in the status bar through a thread.
    """
    cur_time = dt.now().strftime('%H:%M:%S')
    status_bar_right.config(text='|System Time(24h): ' + cur_time)
    status_bar_right.after(1000, status_bar_time_update)

def confirm_button_click(canvas):
    """
    Command function to display user's choice of date and time on the canvas provided.

    :param canvas: canvas object to know which canvas called it and attribute specific
                    configurations to it or some other global variables.
    """
    global date_window, date_window2, selected_date_label, s_time, selected_date_label2, s_time2

    def remove_user_prepended_zeros(var):
        """
        Small Helper function to remove user prepended zeros to time because app will take there of that.

        :param var: spin box value that is passed to function.
        :return: returns new var without the prepended zero.
        """
        if len(var.get()) == 2 and var.get()[0] == '0':
            var.set(var.get()[1])
            return var.get()
        else:
            return var.get()  # do nothing

    # Putting all spinbox values in local variables because
    # its easier to perform operations on those variables.
    top_hour = StringVar()
    top_hour.set(top_hour_spin_box.get())
    top_minute = StringVar()
    top_minute.set(top_minute_spin_box.get())
    m_hour = StringVar()
    m_hour.set(m_hour_spin_box.get())
    m_minute = StringVar()
    m_minute.set(m_minute_spin_box.get())

    # Check for user prepended zeros and remove them with function
    top_hour.set(remove_user_prepended_zeros(top_hour))
    top_minute.set(remove_user_prepended_zeros(top_minute))
    m_hour.set(remove_user_prepended_zeros(m_hour))
    m_minute.set(remove_user_prepended_zeros(m_minute))

    if canvas == top_canvas:

        # If user types in more than 2 numbers, generate error message
        if len(top_hour.get()) > 2 or len(top_minute.get()) > 2:
            messagebox.showerror(title='INVALID INPUT', message='TIME ENTERED IS INVALID')

        else:
            # Prepend 0 if its a single digit
            # Decided to use this and not len() because this helps filter out non-digit input from user :)
            if (0 <= int(top_hour.get()) <= 9) and (0 <= int(top_minute.get()) <= 9):
                s_time = '0' + top_hour.get() + ':' + '0' + top_minute.get()
            elif 0 <= int(top_minute.get()) <= 9:
                s_time = top_hour.get() + ':' + '0' + top_minute.get()
            elif 0 <= int(top_hour.get()) <= 9:
                s_time = '0' + top_hour.get() + ':' + top_minute.get()
            else:
                s_time = top_hour.get() + ':' + top_minute.get()
            selected_date_label.config(text='You selected: ' + date_from_calendar + ' ' + s_time)
            date_window = canvas.create_window(225, 170, window=selected_date_label)

    elif canvas == middle_canvas:

        # If user types in more than 2 numbers, generate error message
        if len(m_hour.get()) > 2 or len(m_minute.get()) > 2:
            messagebox.showerror(title='INVALID INPUT', message='TIME ENTERED IS INVALID')

        else:
            if (0 <= int(m_minute.get()) <= 9) and (0 <= int(m_hour.get()) <= 9):
                s_time2 = '0' + m_hour.get() + ':' + '0' + m_minute.get()
            elif 0 <= int(m_minute.get()) <= 9:
                s_time2 = m_hour.get() + ':' + '0' + m_minute.get()
            elif 0 <= int(m_hour.get()) <= 9:
                s_time2 = '0' + m_hour.get() + ':' + m_minute.get()
            else:
                s_time2 = m_hour.get() + ':' + m_minute.get()
            selected_date_label2.config(text='You selected: ' + date_from_calendar2 + ' ' + s_time2)
            date_window2 = canvas.create_window(225, 170, window=selected_date_label2)

def cancel_button_click(canvas):
    """
    Command function to remove label/text telling user his choice of date and time.

    :param canvas: canvas object to know which canvas called it and update specific labels.
    """
    if canvas == middle_canvas:
        canvas.delete(date_window2)

    else:  # top canvas
        canvas.delete(date_window)

def get_current_date_time(canvas):
    """
    Command function to get the system's current date and time.

    :param canvas: canvas object to know which canvas called it and update its labels.
    """
    global s_time, selected_date_label, date_window, date_window2, s_time2
    global date_from_calendar, date_from_calendar2, selected_date_label2

    if canvas == top_canvas:
        date_from_calendar = dt.now().strftime('%d/%m/%y')
        s_time = dt.now().strftime('%H:%M')
        selected_date_label.config(text='You selected: ' + date_from_calendar + ' ' + s_time)
        date_window = canvas.create_window(225, 170, window=selected_date_label)

    else:  # middle canvas
        date_from_calendar2 = dt.now().strftime('%d/%m/%y')
        s_time2 = dt.now().strftime('%H:%M')
        selected_date_label2.config(text='You selected: ' + date_from_calendar2 + ' ' + s_time2)
        date_window2 = canvas.create_window(225, 170, window=selected_date_label2)

def payment_calculation():
    """
    Function to calculate payment amount based on the date and time passed in
    by user.
    Rate: $5 for 60mins (1hr).
    """
    # Do nothing with dates for now since both dates(date started and date completed) will be the same.
    # NB: If User selects different dates, app only checks the time regardless.
    # Hopefully no one enters different dates and get some crazy output :)
    # Hmmm...naa... TODO: make sure User enters same date twice or only display one calendar button for QoL

    global amount_label, calc_payment_button, amount_to_be_paid, s_time, s_time2, total_hours_spent
    amount_label = bottom_canvas.create_text(250, 150, text='')

    # Perform operations on hours and minutes only
    # Grabbing hours and minutes
    hour_completed = int(s_time2[0:2])
    minutes_completed = int(s_time2[3:])
    hour_started = int(s_time[0:2])
    minutes_started = int(s_time[3:])

    hours_elapsed = hour_completed - hour_started
    minutes_elapsed = minutes_completed - minutes_started

    # Convert everything to minutes and perform calculations based on that
    hours_to_mins = hours_elapsed * 60
    total_minutes_elapsed = minutes_elapsed + hours_to_mins
    total_hours_spent = round(total_minutes_elapsed / 60, 2)
    amount_to_be_paid = round(float(total_minutes_elapsed * 1 / 12), 2)

    # hmm...Might generate false positive errors since user can choose different dates
    # TODO: Do the TODO just before this one
    if total_minutes_elapsed < 0:
        messagebox.showerror(title='INVALID ARGUMENT', message='INVALID INFORMATION ENTERED!')
        calc_payment_button.config(state=DISABLED)
    else:
        amount_label = bottom_canvas.create_text(270, 150, text='$' + str(amount_to_be_paid),
                                                 font=('consolas', 40, 'bold'), fill='#e8ebea')
        calc_payment_button.config(state=DISABLED)

def reset_values():
    """
    Function to reset dates, time and amount calculated.
    """
    global calc_payment_button
    bottom_canvas.delete(amount_label)
    middle_canvas.delete(date_window2)
    top_canvas.delete(date_window)
    calc_payment_button.config(state=NORMAL)

def save_to_excel():
    """
    Function to save date/time started and completed and payment calculated
    to an excel file.
    """
    global excel_first_time_open, amount_to_be_paid, dir_location, total_hours_spent
    program_dir = alpha_current_dir

    def check_total_hours_spent():
        """
        Small Helper function to check if the total hours spent is
         and bring up an error box.
        """
        if total_hours_spent < 0:
            messagebox.showerror(title='INVALID ARGUMENT', message='INVALID INFORMATION ENTERED!')
            return False
        else:
            return True

    def create_new_file():
        """
        Function to create new excel file and input information from app.
        """
        workbook = openpyxl.Workbook()
        sheet = workbook['Sheet']

        # Column headers
        sheet['A1'] = 'Date Started'
        sheet['B1'] = 'Time Started'
        sheet['C1'] = 'Date Completed'
        sheet['D1'] = 'Time Completed'
        sheet['E1'] = 'Total Hours Spent'
        sheet['F1'] = 'Calculated Payment'
        sheet['G1'] = 'Time Info was Saved'

        # Increase width for better view
        for cols in ['A', 'B', 'C', 'D', 'E', 'F', 'G']:
            sheet.column_dimensions[cols].width = 20

        # This is the first time so we can write specifically to certain cells
        sheet['A2'] = date_from_calendar
        sheet['B2'] = s_time
        sheet['C2'] = date_from_calendar2
        sheet['D2'] = s_time2
        sheet['E2'] = total_hours_spent
        sheet['F2'] = '$' + str(amount_to_be_paid)
        sheet['G2'] = dt.now().strftime('%d/%m/%Y, %H:%M:%S')

        # Save excel file and close it
        workbook.save('payment_history.xlsx')
        workbook.close()

        # Change back to program's directory
        os.chdir(program_dir)

        # Message box for successful entry to NEW file
        messagebox.showinfo(title='Save To Excel File', message='Information saved successfully to NEW excel file!')

    def edit_existing_file():
        """
        Function to edit already existing excel file and input info from app.
        """
        workbook = openpyxl.load_workbook('payment_history.xlsx')
        sheet = workbook['Sheet']

        found_available_cell = False
        row_num = 0
        for row in sheet.iter_rows(min_row=1, max_row=200, min_col=1, max_col=6):
            row_num += 1
            for count, cell in enumerate(row):

                # if all cells in a row are empty, then that row is available for writing
                if cell.value is None and cell == row[-1]:
                    sheet.cell(row=row_num, column=1).value = date_from_calendar
                    sheet.cell(row=row_num, column=2).value = s_time
                    sheet.cell(row=row_num, column=3).value = date_from_calendar2
                    sheet.cell(row=row_num, column=4).value = s_time2
                    sheet.cell(row=row_num, column=5).value = total_hours_spent
                    sheet.cell(row=row_num, column=6).value = '$' + str(amount_to_be_paid)
                    sheet.cell(row=row_num, column=7).value = dt.now().strftime('%d/%m/%Y, %H:%M:%S')
                    found_available_cell = True
                    break
                elif cell.value is not None:  # if any cell contains information, break
                    break
            if found_available_cell is True:
                break

        # Save and close excel file
        workbook.save('payment_history.xlsx')
        workbook.close()

        # Change back to program's directory
        os.chdir(program_dir)

        # Message box for successful entry to EXISTING file
        messagebox.showinfo(title='Save To Excel File', message='Information saved successfully to EXISTING excel file!')

    # Main Excel Logic
    if excel_first_time_open == 0:  # Save button has not been clicked already
        if check_total_hours_spent() is True:
            dir_location = askdirectory(initialdir='.\\', title='Select Directory To Save To')

            # Change location to the user-chosen one
            os.chdir(dir_location)

            # Search for already existing file
            for item in os.listdir():
                if item != 'payment_history.xlsx' and item == os.listdir()[-1]:
                    create_new_file()

                elif item == 'payment_history.xlsx':
                    edit_existing_file()
                    break
        else:
            pass  # Show error box and do nothing
    else:
        if check_total_hours_spent() is True:
            # Change location to the user-chosen one
            os.chdir(dir_location)
            edit_existing_file()
        else:
            pass  # Show error box and do nothing

    excel_first_time_open += 1

# TOP CANVAS---------------------------------------------------------------------------------------

top_canvas = Canvas(root, width=ROOT_WIDTH, height=200, borderwidth=0)
top_canvas.grid(row=0, column=0, sticky=W + E, columnspan=2)
img1 = ImageTk.PhotoImage(Image.open('images/clock_dark.jfif'), Image.ANTIALIAS)
top_canvas.create_image(0, 0, image=img1, anchor=NW)
top_canvas.create_text(270, 30, text='Date/Time Started', font=('android 7', 25))

# Top Canvas Hour Spin Box
top_hour_spin_box = Spinbox(top_canvas, from_=0, to=23, width=2, font=('consolas', 20), fg='gray')
top_canvas.create_window(290, 100, window=top_hour_spin_box)
top_hour_spin_box.bind('<Enter>', lambda event: spin_box_hover_in(event, top_hour_spin_box))
top_hour_spin_box.bind('<Leave>', lambda event: spin_box_hover_out(event, top_hour_spin_box))

# Semicolon separating minute from hours
top_canvas.create_text(323, 100, text=':', font=('android 7', 25, 'bold'), fill='white')

# Top Canvas Minute Spin Box
top_minute_spin_box = Spinbox(top_canvas, from_=0, to=59, width=2, font=('consolas', 20), fg='gray')
top_canvas.create_window(355, 100, window=top_minute_spin_box)
top_minute_spin_box.bind('<Enter>', lambda event: spin_box_hover_in(event, top_minute_spin_box))
top_minute_spin_box.bind('<Leave>', lambda event: spin_box_hover_out(event, top_minute_spin_box))

# Time text
top_canvas.create_text(225, 100, text='Time:', font=('consolas', 20))

# Date text
top_canvas.create_text(70, 100, text='Date:', font=('consolas', 20))

# Calendar button for top canvas
t_cal_icon = ImageTk.PhotoImage(Image.open('images/cal2_icon.png').resize((45, 45), Image.ANTIALIAS))
t_calendar_button = Button(top_canvas, image=t_cal_icon, bg='light gray', command=lambda: open_calendar(top_canvas))
cl_window = top_canvas.create_window(136, 100, window=t_calendar_button)
t_calendar_button.bind('<Enter>', lambda event: calendar_button_hover_in(event, top_canvas))
t_calendar_button.bind('<Leave>', lambda event: calendar_button_hover_out(event, top_canvas))

# Selected date and time text
s_time = ''
selected_date_label = Label(top_canvas, text='You selected: ' + date_from_calendar + ' ' + s_time,
                            font=('consolas', 20, 'italic', 'bold'))

# Checkmark button
c_icon = ImageTk.PhotoImage(Image.open('images/check_icon.png').resize((60, 60), Image.ANTIALIAS))
check_button = Button(top_canvas, image=c_icon, command=lambda: confirm_button_click(top_canvas))
cf_window = top_canvas.create_window(490, 90, window=check_button)
check_button.bind('<Enter>', lambda event: confirm_button_hover_in(event, top_canvas))
check_button.bind('<Leave>', lambda event: confirm_button_hover_out(event, top_canvas))

# Cancel button
cancel_icon = ImageTk.PhotoImage(Image.open('images/remove_icon.jpg').resize((60, 60), Image.ANTIALIAS))
cancel_button = Button(top_canvas, image=cancel_icon, command=lambda: cancel_button_click(top_canvas))
cx_window = top_canvas.create_window(490, 160, window=cancel_button)
cancel_button.bind('<Enter>', lambda event: cancel_button_hover_in(event, top_canvas))
cancel_button.bind('<Leave>', lambda event: cancel_button_hover_out(event, top_canvas))

# Get Time Now button
time_now_icon = ImageTk.PhotoImage(Image.open('images/t_now.png').resize((45, 45), Image.ANTIALIAS))
time_now_button = Button(top_canvas, image=time_now_icon, command=lambda: get_current_date_time(top_canvas))
tn_window = top_canvas.create_window(415, 100, window=time_now_button)
time_now_button.bind('<Enter>', lambda event: time_now_button_hover_in(event, top_canvas))
time_now_button.bind('<Leave>', lambda event: time_now_button_hover_out(event, top_canvas))

# MIDDLE CANVAS---------------------------------------------------------------------------------------

middle_canvas = Canvas(root, width=ROOT_WIDTH, height=200, borderwidth=0)
middle_canvas.grid(row=1, column=0, columnspan=2, sticky=W + E)
img2 = ImageTk.PhotoImage(Image.open('images/time_dark.jfif').resize((600, 400)), Image.ANTIALIAS)
middle_canvas.create_image(0, 0, image=img2, anchor=NW)
middle_canvas.create_text(280, 26, text='Date/Time Completed', font=('android 7', 25), fill='#e8ebea')

# Middle Canvas Hour Spin Box
m_hour_spin_box = Spinbox(middle_canvas, from_=0, to=23, width=2, font=('consolas', 20), fg='gray')
m_hour_spin_box.bind('<Enter>', lambda event: spin_box_hover_in(event, m_hour_spin_box))
m_hour_spin_box.bind('<Leave>', lambda event: spin_box_hover_out(event, m_hour_spin_box))
middle_canvas.create_window(290, 100, window=m_hour_spin_box)

# Semicolon separating minute from hours
middle_canvas.create_text(323, 100, text=':', font=('android 7', 25, 'bold'), fill='#e8ebea')

# Middle Canvas Minute Spin Box
m_minute_spin_box = Spinbox(middle_canvas, from_=0, to=59, width=2, font=('consolas', 20), fg='gray')
middle_canvas.create_window(355, 100, window=m_minute_spin_box)
m_minute_spin_box.bind('<Enter>', lambda event: spin_box_hover_in(event, m_minute_spin_box))
m_minute_spin_box.bind('<Leave>', lambda event: spin_box_hover_out(event, m_minute_spin_box))

# Time text
middle_canvas.create_text(225, 100, text='Time:', font=('consolas', 20), fill='#e8ebea')
# this hex colour is used because of the background picture of the canvas.

# Date text
middle_canvas.create_text(70, 100, text='Date:', font=('consolas', 20), fill='#e8ebea')

# Calendar button for middle canvas
m_cal_icon = ImageTk.PhotoImage(Image.open('images/cal2_icon.png').resize((45, 45), Image.ANTIALIAS))
m_calendar_button = Button(middle_canvas, image=m_cal_icon, bg='light gray',
                           command=lambda: open_calendar(middle_canvas))
cl_window2 = middle_canvas.create_window(136, 100, window=m_calendar_button)
m_calendar_button.bind('<Enter>', lambda event: calendar_button_hover_in(event, middle_canvas))
m_calendar_button.bind('<Leave>', lambda event: calendar_button_hover_out(event, middle_canvas))

# Selected date and time text
s_time2 = ''
selected_date_label2 = Label(middle_canvas, text='You selected: ' + date_from_calendar2 + ' ' + s_time2,
                             font=('consolas', 20, 'italic', 'bold'))

# Checkmark icon
c_icon2 = ImageTk.PhotoImage(Image.open('images/check_icon.png').resize((60, 60), Image.ANTIALIAS))
check_button2 = Button(middle_canvas, image=c_icon2, command=lambda: confirm_button_click(middle_canvas))
cf_window2 = middle_canvas.create_window(490, 90, window=check_button2)
check_button2.bind('<Enter>', lambda event: confirm_button_hover_in(event, middle_canvas))
check_button2.bind('<Leave>', lambda event: confirm_button_hover_out(event, middle_canvas))

# Cancel icon
cancel_icon2 = ImageTk.PhotoImage(Image.open('images/remove_icon.jpg').resize((60, 60), Image.ANTIALIAS))
cancel_button2 = Button(middle_canvas, image=cancel_icon2, command=lambda: cancel_button_click(middle_canvas))
cx_window2 = middle_canvas.create_window(490, 160, window=cancel_button2)
cancel_button2.bind('<Enter>', lambda event: cancel_button_hover_in(event, middle_canvas))
cancel_button2.bind('<Leave>', lambda event: cancel_button_hover_out(event, middle_canvas))

# Get Time Now button
time_now_icon2 = ImageTk.PhotoImage(Image.open('images/t_now.png').resize((45, 45), Image.ANTIALIAS))
time_now_button2 = Button(middle_canvas, image=time_now_icon2, command=lambda: get_current_date_time(middle_canvas))
tn_window2 = middle_canvas.create_window(415, 100, window=time_now_button2)
time_now_button2.bind('<Enter>', lambda event: time_now_button_hover_in(event, middle_canvas))
time_now_button2.bind('<Leave>', lambda event: time_now_button_hover_out(event, middle_canvas))

# BOTTOM CANVAS-----------------------------------------------------------------------------------

bottom_canvas = Canvas(root, width=ROOT_WIDTH, height=200, borderwidth=0)
bottom_canvas.grid(row=2, column=0, columnspan=2, sticky=W + E)
img3 = ImageTk.PhotoImage(Image.open('images/money_dark.jfif').resize((600, 400)), Image.ANTIALIAS)
bottom_canvas.create_image(0, 0, image=img3, anchor=NW)
bottom_canvas.create_text(280, 30, text='Payment', font=('android 7', 25), fill='#e8ebea')

# Calculate Payment Button
calc_payment_button = Button(bottom_canvas, text='Calculate Payment', font=('consolas', 20),
                             command=payment_calculation)
bottom_canvas.create_window(275, 100, window=calc_payment_button)
calc_payment_button.bind('<Enter>', calculate_payment_button_hover_in)
calc_payment_button.bind('<Leave>', calculate_payment_button_hover_out)

# Reset Button
reset_icon = ImageTk.PhotoImage(Image.open('images/Reset.png').resize((60, 60), Image.ANTIALIAS))
reset_button = Button(bottom_canvas, image=reset_icon, command=reset_values)
r_window = bottom_canvas.create_window(60, 160, window=reset_button)
reset_button.bind('<Enter>', reset_button_hover_in)
reset_button.bind('<Leave>', reset_button_hover_out)

# Save to Excel Button
save_icon = ImageTk.PhotoImage(Image.open('images/save.png').resize((65, 60), Image.ANTIALIAS))
save_button = Button(bottom_canvas, image=save_icon, command=save_to_excel)
sb_window = bottom_canvas.create_window(490, 160, window=save_button)
save_button.bind('<Enter>', save_button_hover_in)
save_button.bind('<Leave>', save_button_hover_out)

# STATUS BAR--------------------------------------------------------------------------------
global left_status_text, date_and_time_text, status_bar

# Left Status bar text
status_bar_left = Label(root, text=rate_text, font=('consolas', 12), bg='#5fc29e', anchor=W)
status_bar_left.grid(row=3, column=0, sticky=W)

# Right status bar text
status_bar_right = Label(root, text='', font=('consolas', 12), bg='#5fc29e', anchor=E)
status_bar_right.grid(row=3, column=1, sticky=E)

# Thread for status bar time
threading.Thread(target=status_bar_time_update).start()

# MAIN--------------------
if __name__ == '__main__':
    root.mainloop()


import torch
import torch.nn as nn
import torch.nn.functional as F

from model import text_feature_propagation, layernorm_gru

class HiAGM(nn.Module):
    def __init__(self, config, label_ids):
        super(HiAGM, self).__init__()
        self.config = config
        self.label_ids = label_ids
        # TEXT ENCODER (BERT-BASED LANGUAGE MODEL)
        self.text_embedder = torch.hub.load('huggingface/pytorch-transformers', 'model', self.config.model.embedding.type)
        for p in self.text_embedder.parameters():
            p.requires_grad = False
        # EMBEDDING LAYER THAT CONNECTS TEXT ENCODING AND RECURRENT LAYERS
        if (self.config.model.embedding.dimension != self.config.model.rnn.in_dimension) or self.config.model.embedding.additional_layer:
            self.embedding_layer = nn.Linear(
                in_features=self.config.model.embedding.dimension,
                out_features=self.config.model.rnn.in_dimension,
                bias = False
            )
        else:
            self.embedding_layer = nn.Identity()
        self.embedding_dropout = nn.Dropout(self.config.model.embedding.dropout)
        # RECURRENT LAYERS FOR TEXT ENCODING
        if self.config.model.rnn.layernorm:
            self.text_encoder_rnn = layernorm_gru.LayerNormGRU(
                input_size=self.config.model.rnn.in_dimension,
                hidden_size=self.config.model.rnn.out_dimension,
                num_layers=self.config.model.rnn.layers,
                bidirectional=self.config.model.rnn.bidirectional,
                batch_first=True
            )
        else:
            self.text_encoder_rnn = nn.GRU(
                input_size=self.config.model.rnn.in_dimension,
                hidden_size=self.config.model.rnn.out_dimension,
                num_layers=self.config.model.rnn.layers,
                bidirectional=self.config.model.rnn.bidirectional,
                batch_first=True
            )
        self.rnn_dropout= nn.Dropout(p=self.config.model.rnn.dropout)
        # CONVOLUTIONAL LAYERS WITH TOP K POOLING FOR TEXT ENCODING

        self.text_encoder_cnns = nn.ModuleList()
        self.layernorms = nn.ModuleList()
        for kernel in self.config.model.cnn.kernels:
            self.text_encoder_cnns.append(
                torch.nn.Conv1d(
                    in_channels=self.config.model.rnn.out_dimension * 2 if self.config.model.rnn.bidirectional else self.config.model.rnn.out_dimension,
                    out_channels=self.config.model.cnn.dimension,
                    kernel_size=kernel,
                    padding=kernel // 2
                )
            )
            if self.config.model.cnn.layernorm:
                self.layernorms.append(nn.LayerNorm(self.config.model.cnn.dimension, elementwise_affine=False))
        self.k = self.config.model.cnn.pooling_k
        # FEATURE AGGREGATION
        self.information_aggregation = text_feature_propagation.TextFeaturePropagation(self.config, self.label_ids)

    def forward(self, batch):
        # TEXT ENCODER
        self.text_embedder.eval()
        with torch.no_grad():
            text_embedding = self.text_embedder(batch[0], batch[1], batch[2])['last_hidden_state'] # B*L*D(BERT)
        embedding = self.embedding_layer(text_embedding) # B*L*D(BERT)
        embedding = self.embedding_dropout(embedding)
        if torch.cuda.is_available():
            lengths = batch[4].cpu()
        else:
            lengths = batch[4]
        # RECURRENT LAYERS
        if self.config.model.rnn.layernorm:
            rnn_output = self.text_encoder_rnn(embedding, lengths)
        else:
            rnn_input = nn.utils.rnn.pack_padded_sequence(embedding, lengths, batch_first=True, enforce_sorted=False)
            rnn_output, _ = self.text_encoder_rnn(rnn_input)
            rnn_output, _ = nn.utils.rnn.pad_packed_sequence(rnn_output, batch_first=True) # B*L*D(RNN)
        rnn_output = rnn_output.transpose(1, 2) # B*D(RNN)*L
        rnn_output = self.rnn_dropout(rnn_output)
        # CONVOLUTIONAL LAYERS
        cnn_output = []
        for i in range(len(self.text_encoder_cnns)):
            out = self.text_encoder_cnns[i](rnn_output)
            if self.config.model.cnn.layernorm:
                out = self.layernorms[i](out.transpose(1, 2)).transpose(1, 2)
            out = torch.topk(out, self.k)[0] # B*kD(CNN)
            out = F.relu(out) # B*kD(CNN)
            cnn_output.append(out.view(out.size(0), -1)) # B*kD(CNN)
        cnn_output = torch.stack(cnn_output, 1) # B*(#CNN)k*D(CNN)
        # FEATURE AGGREGATION (LOGIT CALCULATION)
        logits = self.information_aggregation(cnn_output)

        return logits


import json
import sys

if len(sys.argv) == 2:
    filename = sys.argv[1]
else:
    print("Error - please specify one file name with the combined JSON")

with open(filename) as f:
    data_json = json.load(f)

def show_tutor_slots(tutor_name):
    tutor = [d for d in data_json["tutors"] if d["name"].startswith(tutor_name)][0]

    timeslots = tutor["timeSlots"]
    officepref = tutor["officePrefs"]

    slots = data_json["slots"]

    for i, (t, o) in enumerate(zip(timeslots, officepref)):
        if t > 0 and o >= 0:
            curr_slot = slots[i]
            assert curr_slot["sid"] == i, "sid != i, {} != {}".format(curr_slot["sid"], i)
            print("Time Pref:", t)
            print("Office Pref:", o)
            print(curr_slot)
            print()

print("Please use the function \"show_tutor_slots(tutor_name)\"")


from bll.bll_base import BllBase
from eb_utils import http_helper
from entity.admin_login_log_model import AdminLoginLogModel

class AdminLoginLog(BllBase[AdminLoginLogModel]):
    def new_instance(self) -> AdminLoginLogModel:
        return AdminLoginLogModel()

    def add_log(self,user_name: str, ni_name: str,  title: str, content: str,user_id=''):
        model = self.new_instance()
        model.title = title
        model.description = content
        model.user_id = user_id
        model.user_name = user_name
        model.ni_name = ni_name
        model.ip_addr = http_helper.get_ip()

        self.add(model)


# Created by Ming Li at 3/7/2019

# Feature:

# Description:

# Contact: ming.li2@columbia.edu

from builtins import range
import numpy as np

def affine_forward(x, w, b):
    """
        Computes the forward pass for an affine function.

        The input x has shape (N, d_1, ..., d_k) and contains a minibatch of N
        examples, where each example x[i] has shape (d_1, ..., d_k). We will
        reshape each input into a vector of dimension D = d_1 * ... * d_k, and
        then transform it to an output vector of dimension M.

        Inputs:
        - x: a numpy array containing input data, of shape (N, d_1, ..., d_k)
        - w: a numpy array of weights, of shape (D, M)
        - b: a numpy array of biases, of shape (M,)

        Returns a tuple of:
        - out: output, of shape (N, M)
    """
    x = x.reshape(x.shape[0], np.prod(x.shape[1:]))
    out = np.dot(x, w) + b
    return out

def affine_backforward(dout, x, w, b):
    """
        Computes the backward pass of an affine function.

        Inputs:
        - dout: upstream derivative, of shape (N, M)
        - cache: Tuple of:
          - x: input data, of shape (N, d_1, ... d_k)
          - w: weights, of shape (D, M)
          - b: bias, of shape (M,)

        Returns a tuple of:
        - dx: gradient with respect to x, of shape (N, d1, ..., d_k)
        - dw: gradient with respect to w, of shape (D, M)
        - db: gradient with respect to b, of shape (M,)
    """
    x = x.reshape(x.shape[0], np.prod(x.shape[1:]))
    dx = np.dot(dout, w.T)
    dw = np.dot(w.T, dout)
    db = dout.T.sum(axis=1)
    return dx, dw, db


import pygame

class Obstaculo(pygame.sprite.Sprite):
    def __init__(self,tamaño,color,x,y):
        super().__init__()
        self.image = pygame.Surface((tamaño,tamaño))
        self.image.fill(color)
        self.rect = self.image.get_rect(topleft = (x,y))

forma = [  #La figura del obstaculo
"  0000000",
" 000000000",
"00000000000",
"00000000000",
"00000000000",
"000     000",
"00       00",]


import collections
import math
import pickle
import string
import sys

# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
# Make a class of inverted index in which all the process is present to find inverted index
class InvertedIndex():
    # This is an constructor which will start fetch the document word and finally will make an inverted index
    def __init__(self):
        self.exclude = list(string.punctuation)
        self.exclude.append('।')

    def make_index(self, input_file, index_filename='index.txt'):
        # open file to write
        self.index_file = open(index_filename, 'w')
        # self.indexDict is an dictionary to store the posting list
        self.indexDict = {}
        with open(input_file, 'r') as corpus:
            sentences = corpus.readlines()
            self.N = len(sentences)
            for i in range(0, len(sentences)):
                text = sentences[i]
                # call the first operation(Remove punctuation from the document) and update the value of text variable
                text = self.remove_punctuation(text)
                # call the second operation(Remove stop word from the document) and store it as array of words
                words = self.remove_stop_word(text)
                # call the operation to apply stemming on word and store it with their document name in posting list
                self.make_PostingList(words, i)
        for i in self.indexDict.keys():
            self.index_file.write(i + "\t\t\t")
            self.index_file.write(str(self.indexDict.get(i)) + "\n")
        self.index_file.close()
        return self.indexDict

    # An operation to apply stemming and store word with their document id and frequency
    def make_PostingList(self, words, doc_id):
        # appy stemming on each word and store in lsit(word)
        words = [self.stem_word(word) for word in words]
        unique_words_frequency_dict = dict(collections.Counter(words))
        for unique_word in unique_words_frequency_dict.keys():
            # if word was present in another document then
            # append the current document at that place otherwise make another row and store it there

            if str(unique_word) in self.indexDict.keys():
                self.indexDict[str(unique_word)][doc_id] = unique_words_frequency_dict[unique_word]
            else:
                self.indexDict[str(unique_word)] = {}
                self.indexDict[str(unique_word)][doc_id] = unique_words_frequency_dict[unique_word]

# An operation to remove stop word
    def remove_stop_word(self, text):
        # print("removing stop word")
        # listOut all the stop word in stopWord list
        stopWord = open("stopWord.txt", "r").read().split("\n")
        text3 = []
        # print(stopWord)
        text = text.split()
        # make condition if text are stop word then it will remove
        for c in text:
            if c not in stopWord:
                text3.append(c)
        return text3

    # An operation to remove punctuation
    def remove_punctuation(self, text):
        text = "".join(c for c in text if c not in self.exclude)
        return text

    # >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    # An operation to find the root word by doing stemming
    # (Taken from Github)
    def stem_word(self, word):
        # suffixes have all the suffixes of hindi word which mostly comes at the end of word
        suffixes = {
            1: ["ो", "े", "ू", "ु", "ी", "ि", "ा"],
            2: ["कर", "ाओ", "िए", "ाई", "ाए", "ने", "नी", "ना", "ते", "ीं", "ती", "ता", "ाँ", "ां", "ों", "ें"],
            3: ["ाकर", "ाइए", "ाईं", "ाया", "ेगी", "ेगा", "ोगी", "ोगे", "ाने", "ाना", "ाते", "ाती", "ाता", "तीं", "ाओं",
                "ाएं", "ुओं", "ुएं", "ुआं"],
            4: ["ाएगी", "ाएगा", "ाओगी", "ाओगे", "एंगी", "ेंगी", "एंगे", "ेंगे", "ूंगी", "ूंगा", "ातीं", "नाओं", "नाएं",
                "ताओं", "ताएं", "ियाँ", "ियों", "ियां"],
            5: ["ाएंगी", "ाएंगे", "ाऊंगी", "ाऊंगा", "ाइयाँ", "ाइयों", "ाइयां"],
        }

        # make condition if word will end with these suffixes then that part will remove
        for k in 5, 4, 3, 2, 1:
            if len(word) > k:
                # print('h')
                for s in suffixes[k]:
                    if word.endswith(s):
                        # print(sf)
                        return (word[:-k])

        return word

    def search(self, query, indexDict, N):
        def getkey(item):
            return temp_calc[item]
        query = self.remove_punctuation(query)
        query = self.remove_stop_word(query)
        # query1 = list(set(query))
        temp_calc = {}
        for q_term in query:
            if q_term in indexDict:
                idf = math.log(N / len(indexDict[q_term].keys()))
                for doc_id in indexDict[q_term].keys():
                    term_frequency = indexDict[q_term][doc_id]
                    if doc_id in temp_calc:
                        temp_calc[doc_id] += term_frequency * idf
                    else:
                        temp_calc[doc_id] = term_frequency * idf
        sorted_result = [(doc, temp_calc[doc]) for doc in sorted(temp_calc, key=getkey, reverse=True)]
        return sorted_result

if __name__ == "__main__":
    # finally call all the operation by making the object of the class
    import os

    # obj = invertedIndex()
    # obj.make_index('hindi.corpus')
    # # finally dump the posting list in wordIndex.p in binary format
    # pickle.dump(obj.indexDict, open("hindi_corpus_index.pickle", "wb"))
    if not os.path.isfile("hindi_corpus_index.pickle") or len(sys.argv) == 2:
        obj = InvertedIndex()
        # finally dump the posting list in wordIndex.p in binary format
        hindi_corpus_file_name = sys.argv[1]
        pickle.dump(obj.make_index('corpus.hi'), open("hindi_corpus_index.pickle", "wb"))
    indexDict = {}
    indexDict = pickle.load(open(hindi_corpus_file_name, "rb"))
    print(len(indexDict))
    obj = InvertedIndex()
    query = input("enter Your Query\n")
    print(search(query, indexDict, N=30010.0))


from flask import jsonify, request
import json
import pymongo

class UserController:
    def __init__(self, user_service):
        self.user_service = user_service

    def find_all_users(self):
        result = self.user_service.find_all_users()

        return json.dumps(result, default=str), 200

    def create_users(self):
        try:
            name = request.json["name"]
            contacts = request.json['contacts']

            user = {"name": name, "contacts": contacts}

            id = self.user_service.create_users(user)

            return jsonify({"data": {**user, "_id": str(id)}}), 201

        except pymongo.errors.DuplicateKeyError:
            return jsonify({"message": "Id já existe"}), 401

        except (KeyError, ValueError):
            return jsonify({"message": "Todos os campos devem ser preenchidos"}), 401

    def update_user(self, id):
        try:
            name = request.json["name"]
            contacts = request.json['contacts']

            user = {"name": name, "contacts": contacts}

            self.user_service.update_user(id, user)

            return jsonify({"message": "Update True"}), 200
        except KeyError:
            return jsonify({"message": "Todos os campos devem ser preenchidos"}), 401
        except ValueError as error:
            message, code = str(error).split("|")

            return jsonify({"message": message}), code

    def delete_user(self, user_id):
        try:
            self.user_service.delete_user(user_id)

            return jsonify({"message": "Delete True"}), 200
        except ValueError as error:
            message, code = str(error).split("|")

            return jsonify({"message": message}), code


from __future__ import annotations
import math

class SnailNumber:
    def __init__(self, a=0, b=0, depth=0, side=-1):
        if isinstance(a, SnailNumber):
            a.nesting_depth += 1
            a.parent = self
            a.side = 0
        if isinstance(b, SnailNumber):
            b.nesting_depth += 1
            b.parent = self
            b.side = 1
        self.pair: list[SnailNumber,SnailNumber] = [a,b]
        self._nesting_depth = depth
        self.side = 0
        self.parent: SnailNumber = None

    @staticmethod
    def load_from_text(text:str):
        if not "," in text:
            return int(text)
        content = text[1:-1] # remove brackets
        #find unnested comma
        depth = 0
        comma_idx = 0
        for i,c in enumerate(content):
            if c == "," and depth == 0:
                comma_idx = i
                break
            elif c == "[":
                depth += 1
            elif c == "]":
                depth -= 1
        return SnailNumber(
            SnailNumber.load_from_text(content[:comma_idx]),
            SnailNumber.load_from_text(content[comma_idx+1:])
        )

    def __str__(self):
        return "[" + str(self.pair[0]) + "," + str(self.pair[1]) + "]"

    def __add__(self, b):
        return SnailNumber(self, b)

    @property
    def nesting_depth(self):
        return self._nesting_depth

    @nesting_depth.setter
    def nesting_depth(self, value):
        self._nesting_depth = value
        for el in self.pair:
            if isinstance(el, SnailNumber):
                el.nesting_depth = value + 1

        # if self.nesting_depth >= 4:
        #     self.explode()

    def explode(self):
        if self.nesting_depth == 4:
            return [self.pair,[1,1],True]
        expl = [None,[0,0],False]

        if isinstance(self.pair[0], SnailNumber):
            expl = self.pair[0].explode()
            if self.apply_explosion(expl, 1):
                if sum(expl[1]) == 0:
                    return [None,[],True]
                else:
                    return expl

        if isinstance(self.pair[1], SnailNumber) and expl[2] is False:
            expl = self.pair[1].explode()
            if self.apply_explosion(expl, 0):
                if sum(expl[1]) == 0:
                    return [None,[],True]
                else:
                    return expl

        return expl

    def apply_explosion(self, expl, side):
        applied = False
        #if self.side == side: return False
        if expl[0] is not None and expl[1][side]:
            if self.nesting_depth == 3:
                self.pair[side-1] = 0
            if isinstance(self.pair[side], int):
                self.pair[side] += expl[0][side]
                expl[1][side] = 0
                applied = True
            elif isinstance(self.pair[side],SnailNumber):
                self.pair[side].add_explosion(expl[0][side], side-1)
                expl[1][side] = 0
                applied = True

        return applied

    def add_explosion(self, value, side):
        if isinstance(self.pair[side], SnailNumber):
            self.pair[side].add_explosion(value, side)
        else:
            self.pair[side] += value

    def split(self):
        for i,el in enumerate(self.pair):
            if isinstance(el, SnailNumber):
                if el.split():
                    return True
            else:
                if el >= 10:
                    self.pair[i] = SnailNumber(math.floor(el/2), math.ceil(el/2), self.nesting_depth+1, side=i)
                    return True
                continue
        return False

    def reduce(self):
        did_exp = self.explode()[2]
        if did_exp:
            #print("After explode: ", self)
            return True
        did_spl = self.split()
        if did_spl:
            #print("After split: ", self)
            return True
        return False

    def magnitude(self):
        magn = 0
        for i,el in enumerate(self.pair):
            if isinstance(el, SnailNumber):
                magn += (3-i)*el.magnitude()
            else:
                magn += (3-i)*el
        return magn


import unittest
from sympy import *
from GradientDescent import gradient_descent

ALLOWED_ERROR = 1 * 10**-10

class MyTestCase(unittest.TestCase):

    def test_one_variable(self):
        x = symbols('x')
        function = x**4 - 3*x**3 + 2
        result = gradient_descent(function, x)
        self.assertTrue(result[x] - 2.25 < ALLOWED_ERROR)

if __name__ == '__main__':
    unittest.main()


# importando as bibliotecas utilizadas
import gurobipy as gp
import pulp as pu

# definição da classe ModeloGurobi
class ModeloGurobi():
    # objeto do tipo gp.Model (modelo matemático do problema)
    m = gp.Model("Cobertura")

    # posteriormente este atributo será preenchido com as variáveis de decisão do problema
    x = 0

# definição da classe ModeloCBC
class ModeloCBC():
    # objeto do tipo pu.LpProblem (modelo matemático do problema)
    m = pu.LpProblem("Cobertura", pu.LpMinimize)

    # posteriormente este atributo será preenchido com as variáveis de decisão do problema
    x = 0


import json
import os

import numpy as np
from joblib import Parallel, delayed
from tqdm.auto import tqdm

# List of the supported models to be used with N-gram LM and beam search decoding
SUPPORTED_MODELS = {
    'EncDecCTCModelBPE': 'subword',
    'EncDecCTCModel': 'char',
}

def softmax(x):
    e = np.exp(x - np.max(x))
    return e / e.sum(axis=-1).reshape([x.shape[0], 1])

def read_train_file(path, lowercase: bool = False):
    lines_read = 0
    text_dataset = []

    with open(path, 'r', encoding='utf-8') as f:
        reader = tqdm(iter(lambda: f.readline(), ''), desc="Read 0 lines", unit=' lines')
        for i, line in enumerate(reader):
            if path.endswith('.json'):
                line = json.loads(line)['text']

            line = line.replace("\n", "").strip()
            if lowercase:
                line = line.lower()

            if line:
                text_dataset.append(line)

                lines_read += 1
                if lines_read % 100000 == 0:
                    reader.set_description(f"Read {lines_read} lines")

    return text_dataset

def tokenize_str(texts, tokenizer, offset):
    tokenized_text = []
    for text in texts:
        tok_text = tokenizer.text_to_ids(text)
        tok_text = [chr(token + offset) for token in tok_text]
        tokenized_text.append(tok_text)
    return tokenized_text

def tokenize_text(data, tokenizer, path, chunk_size=8192, buffer_size=32, token_offset=100):
    dataset_len = len(data)
    print(
        f"Chunking {dataset_len} rows into {dataset_len / float(chunk_size):0.4f} tasks (each chunk contains {chunk_size} elements)"
    )

    current_step = 0
    if os.path.exists(path):
        print(f"Deleting previous file : {path}")
        os.remove(path)

    with Parallel(n_jobs=-2, verbose=10) as parallel:
        while True:
            start = current_step * chunk_size
            end = min((current_step + buffer_size) * chunk_size, dataset_len)

            tokenized_data = parallel(
                delayed(tokenize_str)(data[start : start + chunk_size], tokenizer, token_offset)
                for start in range(start, end, chunk_size)
            )

            # Write dataset
            write_dataset(tokenized_data, path)
            current_step += len(tokenized_data)
            print(f"Finished writing {len(tokenized_data)} chunks to {path}. Current chunk index = {current_step}")
            del tokenized_data
            if end >= dataset_len:
                break

def write_dataset(chunks, path):
    basedir = os.path.dirname(path)

    if not os.path.exists(basedir):
        os.makedirs(basedir, exist_ok=True)

    with open(path, 'a+', encoding='utf-8') as f:
        for chunk_idx in tqdm(range(len(chunks)), desc='Chunk ', total=len(chunks), unit=' chunks'):
            for text in chunks[chunk_idx]:
                line = ' '.join(text)
                f.write(f"{line}\n")


import sys
sys.stdin = open('1213.txt', 'r', encoding='UTF8')

T = 10
for tc in range(T):
    case = '#' + input()
    string = input()
    sentence = input()
    cnt = 0
    for i in range(len(sentence) - len(string) + 1):
        # 문장의 특정 문자부터 문자열의 길이만큼 비교하면서 다른 값이 나오면 반복 종료
        for j in range(len(string)):
            if string[j] != sentence[i + j]:
                break
        # 반복문이 이상없이 수행되었다면 카운트
        else:
             cnt += 1

    print('{} {}'.format(case, cnt))