import helper_const
import helper_global
import helper_database
import telegram
import threading
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import MessageHandler, Filters
from ninesix import Logger

def add_record(bot, channel_id, msg_id, message):
    logger = Logger.logger
    ori_msg_id = message.message_id
    user = message.from_user
    username = user.username
    user_id = user.id
    name = user.first_name
    if user.last_name:
        name += " " + user.last_name
    date = message.date.strftime("%Y-%m-%d %H:%M:%S")

    msg_type = "text"
    msg_content = ""
    media_id = ""
    if message.text:
        msg_type = "text"
        msg_content = message.text
    elif message.sticker:
        msg_type = 'sticker'
        media_id = message.sticker.file_id
        if message.sticker.emoji:
            msg_content = message.sticker.emoji
    elif message.photo:
        msg_type = 'photo'
        media_id = message.photo[-1].file_id
        if message.caption:
            msg_content = message.caption
    elif message.video:
        msg_type = 'video'
        media_id = message.video.file_id
        if message.caption:
            msg_content = message.caption
    elif message.document:
        msg_type = 'document'
        media_id = message.document.file_id
        if message.document.file_name:
            msg_content = message.document.file_name
    elif message.audio:
        msg_type = 'audio'
        media_id = message.audio.file_id
        if message.audio.title:
            msg_content = message.audio.title
    elif message.voice:
        msg_type = 'voice'
        media_id = message.voice.file_id

    msg_content = helper_global.parse_entity(msg_content, message.entities)

    if message.reply_markup and message.reply_markup.inline_keyboard:
        keyboard = message.reply_markup.inline_keyboard
        if len(keyboard) > 0 and len(keyboard[0]) > 0:
            query = keyboard[0][0]
            if query.callback_data:
                args = query.callback_data.split(",")
                if len(args) == 4 and args[0] == "notify":
                    target_channel_id = int(args[1])
                    target_msg_id = int(args[2])
                    target_row_id = args[3]
                    record = helper_database.get_record_by_rowid(target_row_id)
                    if len(record) > 0 and target_channel_id == int(record[0][0]) and target_msg_id == int(record[0][1]):
                        target_name = record[0][3]
                        target_user_id = record[0][8]
                        config = helper_database.get_channel_config(channel_id)
                        if config is None:
                            return
                        channel_lang, channel_username = config[1], config[4]
                        msg_content = "<b>(➤%s) </b> " % target_name.replace('<', '<').replace('>', '>') + msg_content
                        if channel_username is not None:
                            logger.msg({
                                "channel_id": channel_id,
                                "msg_id": msg_id,
                                "target_user": target_user_id,
                                "action": "notify reply"
                            }, tag="private", log_level=80)
                            bot.send_message(
                                chat_id=target_user_id,
                                text=helper_global.value("new_reply_message", "You receive a reply message.", lang=channel_lang) + "\n" + helper_global.value("target_message", "", lang=channel_lang) + "https://t.me/%s/%d" % (channel_username, target_msg_id)
                            )
                        else:
                            link_id = abs(channel_id) % 10000000000
                            bot.send_message(
                                chat_id=target_user_id,
                                text=helper_global.value("new_reply_message", "You receive a reply message.", lang=channel_lang) + "\n" + helper_global.value("target_message", "", lang=channel_lang) + "https://t.me/c/%d/%d" % (link_id, target_msg_id)
                            )

    return helper_database.add_record(channel_id, msg_id, username, name, msg_type, msg_content, media_id, date, user_id, ori_msg_id)

def update_comments(bot, channel_id, msg_id, update_mode):
    logger = Logger.logger
    config = helper_database.get_channel_config(channel_id)
    if config is None:
        return
    channel_lang = config[1]
    mode = config[2]
    recent = config[3]
    logger.msg({
        "channel_id": channel_id,
        "msg_id": msg_id,
        "update_mode": update_mode
    }, tag="private", log_level=80)

    # For mode 3
    if mode == 3 and update_mode == 2:
        buttons = helper_database.get_button_options(channel_id, msg_id)
        stat = helper_database.get_reaction_stat(channel_id, msg_id)
        # Prepare Keyboard
        motd_keyboard = [[
            InlineKeyboardButton(
                value + (" (%d)" % stat[idx] if idx in stat else ""),
                callback_data="like,%s,%s,%d" % (channel_id, msg_id, idx)
            )
        for idx, value in enumerate(buttons)]] + [[
            InlineKeyboardButton(
                helper_global.value("add_comment", "Add Comment", lang=channel_lang),
                url="http://telegram.me/%s?start=add_%d_%d" % (helper_global.value('bot_username', ''), channel_id, msg_id)
            ),
            InlineKeyboardButton(
                helper_global.value("show_all_comments", "Show All", lang=channel_lang),
                url="http://telegram.me/%s?start=show_%s_%d" % (helper_global.value('bot_username', ''), channel_id, msg_id)
            )
        ]]
        motd_markup = InlineKeyboardMarkup(motd_keyboard)

        records = helper_database.get_recent_records(channel_id, msg_id, recent)
        origin_post = helper_database.get_origin_post(channel_id, msg_id)

        try:
            bot.edit_message_caption(
                caption=origin_post + "\n\n" + helper_global.records_to_str(records, channel_lang),
                chat_id=channel_id,
                message_id=msg_id,
                parse_mode=telegram.ParseMode.HTML,
                reply_markup=motd_markup
            )
        except:
            bot.edit_message_text(
                text=origin_post + "\n\n" + helper_global.records_to_str(records, channel_lang),
                chat_id=channel_id,
                message_id=msg_id,
                parse_mode=telegram.ParseMode.HTML,
                reply_markup=motd_markup
            )
        return

    # update comments in channel
    comment_id = helper_database.get_comment_id(channel_id, msg_id)
    if comment_id is None:
        # If no comment message, just update Like buttons
        buttons = helper_database.get_button_options(channel_id, msg_id)
        stat = helper_database.get_reaction_stat(channel_id, msg_id)
        # Prepare Keyboard
        motd_keyboard = [[
            InlineKeyboardButton(
                value + (" (%d)" % stat[idx] if idx in stat else ""),
                callback_data="like,%s,%s,%d" % (channel_id, msg_id, idx)
            )
        for idx, value in enumerate(buttons)]] + [[
            InlineKeyboardButton(
                helper_global.value("add_comment", "Add Comment", lang=channel_lang),
                url="http://telegram.me/%s?start=add_%d_%d" % (helper_global.value('bot_username', ''), channel_id, msg_id)
            ),
            InlineKeyboardButton(
                helper_global.value("show_all_comments", "Show All", lang=channel_lang),
                url="http://telegram.me/%s?start=show_%s_%d" % (helper_global.value('bot_username', ''), channel_id, msg_id)
            )
        ]]
        motd_markup = InlineKeyboardMarkup(motd_keyboard)
        bot.edit_message_reply_markup(
            chat_id=channel_id,
            message_id=msg_id,
            reply_markup=motd_markup
        )
        return

    # Otherwise
    # Update Like buttons
    if update_mode == 0:
        buttons = helper_database.get_button_options(channel_id, msg_id)
        stat = helper_database.get_reaction_stat(channel_id, msg_id)
        # Prepare Keyboard
        motd_keyboard = [[
            InlineKeyboardButton(
                value + (" (%d)" % stat[idx] if idx in stat else ""),
                callback_data="like,%s,%s,%d" % (channel_id, msg_id, idx)
            )
        for idx, value in enumerate(buttons)]]
        if comment_id - msg_id > 1:
            link_id = abs(channel_id) % 10000000000
            motd_keyboard += [[
                InlineKeyboardButton(
                    helper_global.value("jump_to_comment", "Jump to Comment", lang=channel_lang),
                    url="https://t.me/c/%d/%d" % (link_id, comment_id)
                )
            ]]
        motd_markup = InlineKeyboardMarkup(motd_keyboard)
        bot.edit_message_reply_markup(
            chat_id=channel_id,
            message_id=msg_id,
            reply_markup=motd_markup
        )
        return

    # Update comment message
    records = helper_database.get_recent_records(channel_id, msg_id, recent)

    # Prepare Keyboard
    motd_keyboard = [[
        InlineKeyboardButton(
            helper_global.value("add_comment", "Add Comment", lang=channel_lang),
            url="http://telegram.me/%s?start=add_%d_%d" % (helper_global.value('bot_username', ''), channel_id, msg_id)
        ),
        InlineKeyboardButton(
            helper_global.value("show_all_comments", "Show All", lang=channel_lang),
            url="http://telegram.me/%s?start=show_%s_%d" % (helper_global.value('bot_username', ''), channel_id, msg_id)
        )
    ]]
    motd_markup = InlineKeyboardMarkup(motd_keyboard)

    bot.edit_message_text(
        text=helper_global.records_to_str(records, channel_lang),
        chat_id=channel_id,
        message_id=comment_id,
        parse_mode=telegram.ParseMode.HTML,
        reply_markup=motd_markup
    )

def update_dirty_list():
    lock.acquire()
    dirty_list = helper_global.value("dirty_list", [])
    bot = helper_global.value("bot", None)
    for item in dirty_list:
        channel_id, msg_id, update_mode = item
        threading.Thread(
            target=update_comments,
            args=(bot, channel_id, msg_id, update_mode)
        ).start()
    helper_global.assign("dirty_list", [])
    lock.release()

def check_channel_message(bot, message):
    logger = Logger.logger
    chat_id = message.chat_id
    if not message.forward_from_chat:
        helper_global.send_intro_template(bot, chat_id, helper_const.DEFAULT_LANG, "register_invalid", "register_cmd_invalid")
        return
    chat_type = message.forward_from_chat.type
    if not chat_type == "channel":
        helper_global.send_intro_template(bot, chat_id, helper_const.DEFAULT_LANG, "register_invalid", "register_cmd_invalid")
        return
    channel_username = message.forward_from_chat.username
    channel_id = message.forward_from_chat.id
    user_id = message.from_user.id
    logger.msg({
        "user_id": chat_id,
        "channel_id": channel_id,
        "action": "check channel"
    }, tag="private", log_level=90)
    bot_id = int(helper_const.BOT_TOKEN.split(":")[0])
    try:
        chat_members = bot.get_chat_administrators(chat_id=channel_id).result()
        chat_member_ids = [member.user.id for member in chat_members]
        if not user_id in chat_member_ids:
            helper_global.send_intro_template(bot, chat_id, helper_const.DEFAULT_LANG, "register_not_admin", "register_cmd_not_admin")
            return
        for member in chat_members:
            if member.user.id == bot_id:
                post_permission = member.can_post_messages if member.can_post_messages else False
                edit_permission = member.can_edit_messages if member.can_edit_messages else False
                delete_permission = member.can_delete_messages if member.can_delete_messages else False
                if not post_permission or not edit_permission or not delete_permission:
                    helper_global.send_intro_template(bot, chat_id, helper_const.DEFAULT_LANG, "register_no_permission", "register_cmd_no_permission")
                    return
                break
    except:
        helper_global.send_intro_template(bot, chat_id, helper_const.DEFAULT_LANG, "register_no_info", "register_cmd_no_info")
        return
    try:
        helper_database.add_channel_config(channel_id, helper_const.DEFAULT_LANG, 1, 10, channel_username, chat_id, 1, 1)
    except:
        helper_global.assign(str(chat_id) + "_status", "0,0")
        helper_global.send_intro_template(bot, chat_id, helper_const.DEFAULT_LANG, "register_failed", "register_cmd_failed")
        return

    helper_global.assign(str(chat_id) + "_status", "0,0")
    helper_global.send_intro_template(bot, chat_id, helper_const.DEFAULT_LANG, "register_success", "register_cmd_success")

def private_msg(bot, update):
    logger = Logger.logger
    message = update.edited_message if update.edited_message else update.message
    chat_id = message.chat_id
    args = helper_global.value(str(chat_id) + "_status", "0,0")
    logger.msg({
        "user_id": chat_id,
        "status": args
    }, tag="private", log_level=90)

    params = args.split(",")
    channel_id = int(params[0])
    msg_id = int(params[1])
    if channel_id == 0:
        if msg_id == 1:
            check_channel_message(bot, message)
        return

    # Check comment message
    comment_exist = helper_database.check_reflect(channel_id, msg_id)
    config = helper_database.get_channel_config(channel_id)
    if config is None:
        return
    channel_lang = config[1]
    mode, recent, username, admin_id, notify = config[2], config[3], config[4], config[5], config[6]
    logger.msg({
        "user_id": chat_id,
        "channel_id": channel_id,
        "msg_id": msg_id,
        "action": "add comment"
    }, tag="private", log_level=90)

    # For Auto Mode = 2
    if not comment_exist:
        logger.msg({
            "user_id": chat_id,
            "channel_id": channel_id,
            "msg_id": msg_id,
            "action": "add comment area"
        }, tag="private", log_level=80)
        comment_message = bot.send_message(
            chat_id=channel_id,
            text=helper_global.value("comment_refreshing", "Refreshing...", lang=channel_lang),
            reply_to_message_id=msg_id,
            parse_mode=telegram.ParseMode.HTML
        ).result()
        helper_database.add_reflect(channel_id, msg_id, comment_message.message_id)
        #bot.edit_message_reply_markup(
        #    chat_id=channel_id,
        #    message_id=msg_id,
        #    reply_markup=None
        #)
        update_dirty_msg(channel_id, msg_id, update_mode=0)

    result = add_record(bot, channel_id, msg_id, message)

    # Update Dirty List
    update_dirty_msg(channel_id, msg_id, update_mode=(2 if mode == 3 else 1))
    if notify == 1 and not int(chat_id) == int(admin_id):
        logger.msg({
            "user_id": chat_id,
            "channel_id": channel_id,
            "msg_id": msg_id,
            "admin_id": admin_id,
            "action": "notify channel owner"
        }, tag="private", log_level=80)
        if username is not None:
            bot.send_message(
                chat_id=admin_id,
                text=helper_global.value("new_comment_message", "You have a new comment message.", lang=channel_lang) + "\n" + helper_global.value("target_message", "", lang=channel_lang) + "https://t.me/%s/%d" % (username, msg_id)
            )
        else:
            link_id = abs(channel_id) % 10000000000
            bot.send_message(
                chat_id=admin_id,
                text=helper_global.value("new_comment_message", "You have a new comment message.", lang=channel_lang) + "\n" + helper_global.value("target_message", "", lang=channel_lang) + "https://t.me/c/%d/%d" % (link_id, msg_id)
            )

    if result == 0:
        bot.send_message(chat_id=chat_id, text=helper_global.value("comment_success", "Success!", lang=channel_lang))
    elif result == 1:
        bot.send_message(chat_id=chat_id, text=helper_global.value("comment_edit_success", "Success!", lang=channel_lang))

def update_dirty_msg(channel_id, msg_id, update_mode=1):
    lock.acquire()
    dirty_list = helper_global.value("dirty_list", [])
    if not (channel_id, msg_id, update_mode) in dirty_list:
        dirty_list.append((channel_id, msg_id, update_mode))
    helper_global.assign("dirty_list", dirty_list)
    lock.release()

def set_interval(func, sec):
    def func_wrapper():
        set_interval(func, sec)
        func()
    t = threading.Timer(sec, func_wrapper)
    t.start()
    return t

refresh_status = helper_global.value("refresh_status", False)
if not refresh_status:
    set_interval(update_dirty_list, helper_const.MIN_REFRESH_INTERVAL)
    helper_global.assign("refresh_status", True)
lock = threading.Lock()
_handler = MessageHandler(Filters.private, private_msg, edited_updates=True)


import collections
import functools

class Monkey:
    def __init__(self):
        self.items = collections.deque([])
        self.original_items = []
        self.operation = None
        self.test_number = None
        self.positive_outcome_target = None
        self.negative_outcome_target = None
        self.items_inspected = 0
        self.factor_in_relief = True
        self.custom_reducer = 1

    def choose_target(self) -> int:
        if len(self.items) == 0:
            raise Exception('Cannot choose targets when empty handed')

        return self.positive_outcome_target if self.items[0] % self.test_number == 0 else self.negative_outcome_target

    def has_items(self) -> bool:
        return len(self.items) > 0

    def inspect(self) -> None:
        self.items_inspected += 1
        self.items[0] = self.operation(self.items[0])
        if self.factor_in_relief:
            self.items[0] //= 3
        else:
            self.items[0] %= self.custom_reducer

    def throw(self) -> int:
        return self.items.popleft()

    def catch(self, item: int) -> None:
        self.items.append(item)

    def reset(self):
        self.items = collections.deque(self.original_items.copy())
        self.items_inspected = 0

monkeys = []
initial_items = []
with open('real-input.txt') as f:
    while True:
        monkey_header = f.readline()
        if not monkey_header:
            break

        monkey = Monkey()
        items = list(map(lambda x: int(x.strip()), f.readline().strip().split(':')[1].split(',')))
        monkey.items = collections.deque(items.copy())
        monkey.original_items = items.copy()
        operation = f.readline().split('=')[1].strip()
        operator, constant = operation.split(' ')[1:]

        def generate_operation(monkey_operator: str, operand: str):
            def op(worry_level: int) -> int:
                monkey_constant = worry_level if operand == 'old' else int(operand)

                if monkey_operator == '*':
                    return worry_level * monkey_constant

                if monkey_operator == '+':
                    return worry_level + monkey_constant

                if monkey_operator == '/':
                    return worry_level // monkey_constant

                if monkey_operator == '-':
                    return worry_level - monkey_constant

            return op

        monkey.operation = generate_operation(operator, constant)

        monkey.test_number = int(f.readline().strip().split(' ')[-1])
        monkey.positive_outcome_target = int(f.readline().strip().split(' ')[-1])
        monkey.negative_outcome_target = int(f.readline().strip().split(' ')[-1])

        monkeys.append(monkey)
        f.readline()

for _ in range(20):
    for monkey in monkeys:
        while monkey.has_items():
            monkey.inspect()
            monkeys[monkey.choose_target()].catch(monkey.throw())

inspections = [m.items_inspected for m in monkeys]
inspections.sort()
print('Part 1: ', inspections[-2] * inspections[-1])

part_2_reducer = functools.reduce(lambda c, i: i * c, [m.test_number for m in monkeys], 1)
for monkey in monkeys:
    monkey.reset()
    monkey.factor_in_relief = False
    monkey.custom_reducer = part_2_reducer

for _ in range(10_000):
    for monkey in monkeys:
        while monkey.has_items():
            monkey.inspect()
            monkeys[monkey.choose_target()].catch(monkey.throw())

inspections = [m.items_inspected for m in monkeys]
inspections.sort()
print('Part 2: ', inspections[-2] * inspections[-1])


import threading
import socketserver
import queue
import collections
import cv2
import numpy as np
import math
import socket
import base64
from imutils.perspective import four_point_transform
from scipy.interpolate import interp1d

sensor_data = " "
test_finish = True
control_queue = queue.Queue(128)
web_queue = queue.Queue(1)
sensor_data_queue = collections.deque(maxlen=3)
sign_active_global = False
manual_control = 1  # 0 - autonomous
                    # 0.5 - semi-autonomous
                    # 1 - manueln

class NeuralNetwork(object):

    def __init__(self):
        self.model = None

    def create(self):
        self.model = cv2.ml.ANN_MLP_load('mlp_xml/mlp.xml')

    def predict(self, samples):
        ret, resp = self.model.predict(samples)
        return resp.argmax(-1)

class RCControl(object):

    def __init__(self):
        pass

    def steer(self, prediction):
        global control_queue
        if prediction == 2:
            control_queue.put('1')
            # print("Forward")
        elif prediction == 0:
            control_queue.put('2')
            # print("Forward-Left")
        elif prediction == 1:
            control_queue.put('8')
            # print("Forward-Right")
        else:
            self.stop()

    def stop(self):
        global control_queue
        control_queue.put('0')

class DistanceToCamera(object):

    def __init__(self):
        # camera params
        self.alpha = 8.0 * math.pi / 180
        self.v0 = 119.865631204
        self.ay = 332.262498472

    def calculate(self, v, h, x_shift, image):
        # compute and return the distance from the target point to the camera
        d = h / math.tan(self.alpha + math.atan((v - self.v0) / self.ay))
        return d

class ObjectDetection(object):

    def __init__(self):
        self.red_light = False
        self.green_light = False
        self.yellow_light = False

    # detecting stop traffic sigh
    def detect(self, cascade_classifier, gray_image, image):

        # y camera coordinate of the target point 'P'
        v = 0

        # detection
        cascade_obj = cascade_classifier.detectMultiScale(
            gray_image,
            scaleFactor=1.1,
            minNeighbors=5,
            minSize=(30, 30),
            flags=cv2.CASCADE_SCALE_IMAGE
        )

        # draw a rectangle around the objects
        for (x_pos, y_pos, width, height) in cascade_obj:
            v = y_pos + height - 5

        return v

    # derection for turn left and turn right traffic sign
    def findTrafficSign(self, frame):
        lower_blue = np.array([85, 100, 70])
        upper_blue = np.array([115, 255, 255])

        frameArea = frame.shape[0] * frame.shape[1]

        hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)

        kernel = np.ones((3, 3), np.uint8)
        mask = cv2.inRange(hsv, lower_blue, upper_blue)

        mask = cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel)
        mask = cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel)

        cnts = cv2.findContours(mask.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]

        largestArea = 0
        largestRect = None

        detectedTrafficSign = None

        if len(cnts) > 0:
            for cnt in cnts:
                rect = cv2.minAreaRect(cnt)
                box = cv2.boxPoints(rect)
                box = np.int0(box)

                sideOne = np.linalg.norm(box[0] - box[1])
                sideTwo = np.linalg.norm(box[0] - box[3])

                area = sideOne * sideTwo

                if area > largestArea:
                    largestArea = area
                    largestRect = box

        v = 0

        if largestArea > frameArea * 0.02:
            v = largestRect[1][1] + (largestRect[0][1] - largestRect[1][1]) - 5
            warped = four_point_transform(mask, [largestRect][0])
            detectedTrafficSign = self.identifyTrafficSign(warped)

        return detectedTrafficSign, v

    def identifyTrafficSign(self, image):

        SIGNS_LOOKUP = {
            (1, 0, 0, 1): 'Turn Right',  # turnRight
            (0, 0, 1, 1): 'Turn Left',  # turnLeft
        }

        THRESHOLD = 150

        image = cv2.bitwise_not(image)
        (subHeight, subWidth) = np.divide(image.shape, 10)
        subHeight = int(subHeight)
        subWidth = int(subWidth)

        leftBlock = image[4 * subHeight:9 * subHeight, subWidth:3 * subWidth]
        centerBlock = image[4 * subHeight:9 * subHeight, 4 * subWidth:6 * subWidth]
        rightBlock = image[4 * subHeight:9 * subHeight, 7 * subWidth:9 * subWidth]
        topBlock = image[2 * subHeight:4 * subHeight, 3 * subWidth:7 * subWidth]

        leftFraction = np.sum(leftBlock) / (leftBlock.shape[0] * leftBlock.shape[1])
        centerFraction = np.sum(centerBlock) / (centerBlock.shape[0] * centerBlock.shape[1])
        rightFraction = np.sum(rightBlock) / (rightBlock.shape[0] * rightBlock.shape[1])
        topFraction = np.sum(topBlock) / (topBlock.shape[0] * topBlock.shape[1])

        segments = (leftFraction, centerFraction, rightFraction, topFraction)
        segments = tuple(1 if segment > THRESHOLD else 0 for segment in segments)

        if segments in SIGNS_LOOKUP:
            return SIGNS_LOOKUP[segments]
        else:
            return None

class SensorDataHandler(socketserver.BaseRequestHandler):

    data = " "

    def handle(self):
        global sensor_data
        global sensor_data_queue
        try:
            while self.data:
                self.data = self.request.recv(1024)
                sensor_data_queue.append(self.data)
        except:
            print("Connection closed on thread 2")

class VideoStreamHandler(socketserver.StreamRequestHandler):

    # h1: stop sign
    h1 = 15.5 - 10  # cm
    # h2: traffic light
    h2 = 15.5 - 10

    # create neural network
    model = NeuralNetwork()
    model.create()

    obj_detection = ObjectDetection()
    rc_car = RCControl()

    # cascade classifiers
    stop_cascade = cv2.CascadeClassifier('cascade_xml/stop_sign.xml')

    d_to_camera = DistanceToCamera()
    d_stop_sign = 60

    stop_start = 0              # start time when stop at the stop sign
    stop_finish = 0
    stop_time = 0
    drive_time_after_stop = 0
    sign_start = 0
    drive_time_after_sign = 0

    sensor_x = [25, 30, 43, 60, 87, 165, 255] # values that we get from IR sensors
    sensor_y = [30, 25, 20, 15, 10, 5, 1] # real distance from car in cm

    def recvall(self, sock, count):
        buf = b''
        while count:
            newbuf = sock.read(count)
            if not newbuf:
                return None
            buf += newbuf
            count -= len(newbuf)
        return buf

    def handle(self):
        global test_finish
        global sensor_data
        global sensor_data_queue
        global sign_active_global
        global web_queue
        stop_flag = False
        stop_sign_active = True
        sign_active = False
        sign_sent = False

        interpolate_sensor = interp1d(self.sensor_x, self.sensor_y, kind='cubic')
        interpolate_sensor.bounds_error = False

        try:
            # stream video frames one by one
            sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            while test_finish:
                length = self.recvall(self.rfile, 16)
                stringData = self.recvall(self.rfile, int(length))
                data = np.fromstring(stringData, dtype='uint8')
                image = cv2.imdecode(data, 1)
                gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)

                _, t12 = cv2.imencode('.jpg', image)
                b64_img = base64.b64encode(t12)

                sock.sendto(b64_img, ('localhost', 8993))

                # lower half of the image
                half_gray = gray[120:240, :]

                # object detection
                v_param1 = self.obj_detection.detect(self.stop_cascade, gray, image)
                detected_sign, v_param2 = self.obj_detection.findTrafficSign(image)

                if detected_sign is not None and not sign_active:
                    sign_active = True
                    self.sign_start = cv2.getTickCount()

                # distance measurement
                if v_param1 > 0:
                    self.d_stop_sign = self.d_to_camera.calculate(v_param1, self.h1, 300, image)

                # showing video
                # cv2.imshow('image', image)
                # if cv2.waitKey(1) & 0xFF == ord('q'):
                #     pass

                # stop conditions
                sensor_data = []
                try:
                    sensor_data.append(sensor_data_queue.popleft().decode().split(";"))
                    sensor_data.append(sensor_data_queue.popleft().decode().split(";"))
                    sensor_data.append(sensor_data_queue.popleft().decode().split(";"))
                except:
                    pass

                each_sensor_data = [0, 0, 0, 0, 0]
                for data in sensor_data:
                    each_sensor_data[0] += int(data[0])
                    each_sensor_data[1] += int(data[1])
                    each_sensor_data[2] += int(data[2])
                    each_sensor_data[3] += int(data[3])
                    each_sensor_data[4] += int(data[4])

                if sensor_data:
                    each_sensor_data[0] /= len(sensor_data)
                    each_sensor_data[1] /= len(sensor_data)
                    each_sensor_data[2] /= len(sensor_data)
                    each_sensor_data[3] /= len(sensor_data)
                    each_sensor_data[4] /= len(sensor_data)

                tmp_str = ''
                tmp_str += '%.1f' % interpolate_sensor(each_sensor_data[0]) + ';'
                tmp_str += '%.1f' % interpolate_sensor(each_sensor_data[1]) + ';'
                tmp_str += '%.1f' % interpolate_sensor(each_sensor_data[2]) + ';'
                tmp_str += '%.1f' % interpolate_sensor(each_sensor_data[3]) + ';'
                tmp_str += '%.1f' % interpolate_sensor(each_sensor_data[4]) + ';'
                tmp_str += str(detected_sign) + ';'
                tmp_str += str(True if v_param1 > 0 else False) + '\n'

                if web_queue.empty():
                    web_queue.put(tmp_str)

                # for manuel controll we do not need any AI comands
                if manual_control == 1:
                    continue

                # for semi-autonomous we have collision detection and collision avoidance system
                if manual_control == 0.5:
                    if int(each_sensor_data[2]) > 100:
                        self.rc_car.stop()
                        continue
                    continue

                # reshape image
                image_array = half_gray.reshape(1, 38400).astype(np.float32)

                # neural network makes prediction
                prediction = self.model.predict(image_array)

                if int(each_sensor_data[2]) > 100:
                    # print("Stop, obstacle in front")
                    self.rc_car.stop()

                elif 0 < self.d_stop_sign < 55 and stop_sign_active:
                    # print("Stop sign ahead")
                    self.rc_car.stop()

                    # stop for 5 seconds
                    if stop_flag is False:
                        self.stop_start = cv2.getTickCount()
                        stop_flag = True
                    self.stop_finish = cv2.getTickCount()

                    self.stop_time = (self.stop_finish - self.stop_start)/cv2.getTickFrequency()
                    # print("Stop time: %.2fs" % self.stop_time)

                    # 3 seconds later, continue driving
                    if self.stop_time > 3:
                        # print("Waited for 3 seconds")
                        if each_sensor_data[1] > 35 or each_sensor_data[2] > 50:
                            pass
                        else:
                            stop_flag = False
                            stop_sign_active = False

                elif sign_active and not sign_sent:
                    if detected_sign == 'Turn Left':
                        # print('Turn Left sign')
                        control_queue.put('l')
                        sign_sent = True
                    elif detected_sign == 'Turn Right':
                        # print('Turn Right sign')
                        control_queue.put('r')
                        sign_sent = True

                elif int(each_sensor_data[1]) > 90:
                    if int(each_sensor_data[4]) > 70:
                        # print("Stop front-left obstacle")
                        self.rc_car.stop()
                    else:
                        # print("Avoid front-left obstacle")
                        control_queue.put('8')

                elif int(each_sensor_data[3]) > 90:
                    if int(each_sensor_data[0]) > 70:
                        # print("Stop front-right obstacle")
                        self.rc_car.stop()
                    else:
                        # print("Avoid front-right obstacle")
                        control_queue.put('2')

                else:
                    self.rc_car.steer(prediction)
                    self.stop_start = cv2.getTickCount()
                    self.d_stop_sign = 50
                    self.d_sign = 50

                if stop_sign_active is False:
                    self.drive_time_after_stop = (self.stop_start - self.stop_finish)/cv2.getTickFrequency()
                    if self.drive_time_after_stop > 5:
                        stop_sign_active = True

                if sign_active is True:
                    self.drive_time_after_sign = (cv2.getTickCount() - self.sign_start) / cv2.getTickFrequency()
                    if self.drive_time_after_sign > 3:
                        sign_active = False
                        sign_active_global = False
                        sign_sent = False
                        # print("Finish sign detection")

            sock.close()

        except Exception as e:
            print("Connection closed on thread 1")

class ThreadServer(object):

    def __init__(self):
        # sensor - receive data from RPi
        distance_thread = threading.Thread(target=self.server_thread2, args=('localhost', 8002))
        distance_thread.start()
        # video - receive video (jpeg) images from RPi
        video_thread = threading.Thread(target=self.server_thread, args=('localhost', 8000))
        video_thread.start()
        # keyboard i AI controll
        control_thread = threading.Thread(target=self.server_thread3, args=('localhost', 8004))
        control_thread.start()
        # send to web (sensor & sign) - send to client sensor values and detected traffic sign
        web_data_thread = threading.Thread(target=self.server_thread4, args=('localhost', 8992))
        web_data_thread.start()

    def server_thread(self, host, port):
        server = socketserver.TCPServer((host, port), VideoStreamHandler)
        server.serve_forever()

    def server_thread2(self, host, port):
        server = socketserver.TCPServer((host, port), SensorDataHandler)
        server.serve_forever()

    def server_thread3(self, host, port):
        server_socket = socket.socket()
        server_socket.bind((host, port))
        server_socket.listen(0)
        self.server_thread3_rec(server_socket)

    def server_thread3_rec(self, server_socket):
        global control_queue
        global test_finish
        global sign_active_global
        connection, _ = server_socket.accept()

        try:
            while test_finish:

                cmd = control_queue.get(block=True, timeout=None)

                b = bytearray()
                b.extend(map(ord, cmd))
                if not sign_active_global:
                    connection.send(b)
                if cmd == 'l' or cmd == 'r':
                    sign_active_global = True
                control_queue.task_done()
        except ConnectionResetError:
            print("Connection closed on thread 3")
            self.server_thread3_rec(server_socket)
        except ConnectionAbortedError:
            print("Connection closed on thread 3")
            self.server_thread3_rec(server_socket)

    def server_thread4(self, host, port):

        global manual_control
        global control_queue
        lock = threading.Lock()

        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

        while True:
            try:
                s.connect((host, port))
            except:
                continue
            else:
                break
        s.setblocking(True)
        s.settimeout(0.1)

        while test_finish:
            try:
                cmd = web_queue.get(block=True, timeout=None)
                s.send(cmd.encode())
                web_queue.task_done()

                tmp = s.recv(64).decode()
                tmp = tmp[:-2]

                if tmp == 'manual':
                    lock.acquire()
                    manual_control = 1
                    lock.release()
                    control_queue.put('0')
                    # print('manual')
                    continue
                elif tmp == 'auto':
                    lock.acquire()
                    manual_control = 0
                    lock.release()
                    control_queue.put('0')
                    # print('auto')
                    continue
                elif tmp == 'semi':
                    lock.acquire()
                    manual_control = 0.5
                    lock.release()
                    control_queue.put('0')
                    # print('semi')
                    continue

                if manual_control == 1 or manual_control == 0.5:  # jeste mauelno, i onda nase komande stavljaj u queue
                    if tmp != "":
                        control_queue.put(tmp)
            except socket.error as e:
                if e.errno is None:
                    pass
                if e.errno == 10054:
                    print("Connection closed on thread 4")
                    try:
                        self.server_thread4(host, port)
                    except:
                        pass

if __name__ == '__main__':
    ThreadServer()


import pygame, sys

from game.common.yaml_parsing import YAMLInstancer
from game.subsystems.environment import *
from game.subsystems.entities import *
from pygame.locals import *
from game.common.math import *

BROWN = (150, 75, 0)
LIGHTBROWN = (181, 101, 29)
BLACK = (0,0,0)
RED = (255,0,0)

env = Environment()
DISPLAYSURF = pygame.display.set_mode((100 * len(env.board), 100 * len(env.board[0])))

test_yaml = open('./towerTest.yaml').read()
test_yaml2 = open('./towerTestEnemy.yaml').read()
tower1 = YAMLInstancer.get_single(test_yaml, Tower)
enemy1 = YAMLInstancer.get_single(test_yaml2, Enemy)
proj1 = tower1.fire([enemy1])
print(enemy1)
if not proj1 is None:
    proj1.impact()
print(enemy1)


"""
Write a function that gets file path as an argument.
Read the first line of the file.
If first line is a number return true if number in an interval [1, 3)*
and false otherwise.
In case of any error, a ValueError should be thrown.

Write a test for that function using pytest library.

Definition of done:
 - function is created
 - function is properly formatted
 - function has positive and negative tests
 - all temporary files are removed after test run

You will learn:
 - how to test Exceptional cases
 - how to clean up after tests
 - how to check if file exists**
 - how to handle*** and raise**** exceptions in test. Use sample from the documentation.

* https://en.wikipedia.org/wiki/Interval_(mathematics)#Terminology
** https://docs.python.org/3/library/os.path.html
*** https://docs.python.org/3/tutorial/errors.html#handling-exceptions
**** https://docs.python.org/3/tutorial/errors.html#raising-exceptions
"""

import os
import sys
from typing import Callable

def file_producer(text: str):
    with open("temporary.txt", "w") as f:
        f.write(text)

    def my_wrapper(func: Callable):
        output = func("temporary.txt")
        f.close()
        os.remove("temporary.txt")
        return output

    return my_wrapper

def read_magic_number_only_value_error(path: str) -> bool:
    try:
        with open(path, "r") as fi:
            return is_useful_number(float(fi.readline().strip()))
    except:
        raise ValueError("Somthing came wrong.")

def is_useful_number(number: float) -> bool:
    return 1 <= number < 3

if __name__ == "__main__":
    ...


from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_distances
from sklearn.feature_extraction import stop_words
from nltk.stem.wordnet import WordNetLemmatizer
from nltk.tokenize import word_tokenize
from sklearn.decomposition import NMF
from nltk.corpus import stopwords
from cleaning import Cleaning
import pandas as pd
import numpy as np
import time
import re

def tokenize(doc):
    wordnet = WordNetLemmatizer()
    return [wordnet.lemmatize(word) for word in word_tokenize(doc.lower())]

def find_top_words(matrix):
    words = {}
    for idx, __ in enumerate(matrix):
        top_words = np.argsort(matrix[idx])[-10:][::-1]
        top_words = tf_feature_names[top_words]
        words[idx] = top_words
    return words

def find_similar_wines(wine_title):
    wine_idx = df[df['title'] == wine_title].index[0]
    dists = cosine_distances(W, W)
    top_wines = np.argsort(dists[wine_idx, :])[-10:][::1]
    # top_wines = df.title[top_wines]
    top_wines = df.loc[top_wines][['title', 'description', 'variety']]
    return top_wines[1:]

if __name__ == '__main__':
    raw_data = '../data/winemag-data-190314.csv'
    wine_title = 'Quinta dos Avidagos 2011 Avidagos Red (Douro)'
    num_topics = 7

    # Get clean data descriptions
    cleaning = Cleaning(raw_data)
    cleaning.CreateDataFrame()
    cleaning.CleanDataFrame()
    desc = cleaning.cleansed_data
    df = cleaning.processed_data

    # add morestop words
    stop = list(stop_words.ENGLISH_STOP_WORDS)
    additional_stop = ['ha', 'le', 'u', 'wa', 's', 't', 's', 'r',
                       'ro', 'wine', 'flavor', 'aromas', 'finish',
                       'palate', 'note', 'nose', 'drink', 'ofcut',
                       'feeeling']
    for val in additional_stop:
        stop.append(val)
    stop = frozenset(stop)

    # create TF-IDF vector
    tf_vect = TfidfVectorizer(stop_words=stop, tokenizer=tokenize)
    tf = tf_vect.fit_transform(desc)
    words = tf_vect.get_feature_names()
    tf_feature_names = np.array(words)
    print('Data featurized')

    # create NMF model
    start = time.time()
    nmf = NMF(n_components=num_topics)
    nmf.fit(tf)
    W = nmf.transform(tf)
    H = nmf.components_
    stop = time.time()
    print('Model created in ', stop-start)

    # find top words in each topic
    topic_words = find_top_words(H)
    print(topic_words)

    # find 10 wines similarly loaded to topics
    find_similar_wines(wine_title)


'''
Static files to copy relative to distribution root.

Helper functions to populate ``data_files`` property of ``py2exe`` setup.
'''
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)
import glob
import itertools as it
import os
import re

import path_helpers as ph

__all__ = ['DEFAULT_STATIC_PREFIX', 'get_data_files', 'group_data_files']

DEFAULT_STATIC_PREFIX = {'__core__': {'files': [{'source': '$PY2EXE_HELPERS/scripts/run-in.bat', 'target': 'Scripts/wrappers/conda'},
                                                {'source': 'MSVCP90.dll', 'target': ''},
                                                {'source': '$PY2EXE_HELPERS/scripts/py2exe-python-emulation.bat', 'target': 'etc/conda/activate.d'}]},
                         'microdrop': {'files': [{'source': '$CONDA_PREFIX/etc/conda/', 'target': 'etc/conda'},
                                                 {'source': '$CONDA_PREFIX/etc/microdrop/', 'target': 'etc/microdrop'}]},
                         'nbformat': {'files': [{'source': '$MODULE/', 'target': 'nbformat'}]},
                         'notebook': {'files': [{'source': '$PY2EXE_HELPERS/scripts/jupyter-notebook.py', 'target': 'Scripts'},
                                                {'source': '$MODULE/static/', 'target': 'notebook/static'},
                                                {'source': '$MODULE/templates/', 'target': 'notebook/templates'}]},
                         'numpy': {'files': [{'source': 'libiomp5md.dll', 'target': ''},
                                             {'source': 'mkl_*.dll', 'target': ''}]},
                         'pandoc': {'files': [{'source': '$CONDA_PREFIX/Scripts/pandoc.exe', 'target': 'Scripts'}]},
                         'platformio': {'files': [{'source': '$PY2EXE_HELPERS/scripts/pio.bat', 'target': 'Scripts'},
                                                  {'source': '$MODULE/builder/', 'target': 'platformio/builder'},
                                                  {'source': '$CONDA_PREFIX/share/platformio/', 'target': 'share/platformio'}]},
                         'pygst-0.10': {'module': 'gst', 'files': [{'source': '$MODULE/bin/*.dll', 'target': ''},
                                                                   {'source': '$PY2EXE_HELPERS/scripts/gst-plugins-path.bat', 'target': 'etc/conda/activate.d'},
                                                                   {'source': '$MODULE/plugins/*.dll', 'target': 'gst-plugins'}]},
                         'pygtk2': {'module': 'gtk', 'files': [{'source': '$MODULE/../gtk_runtime/share/themes/MS-Windows/gtk-2.0/', 'target': 'etc/gtk-2.0'},
                                                               {'source': '$MODULE/../gtk_runtime/lib/gtk-2.0/2.10.0/engines/*.dll', 'target': 'lib/gtk-2.0/2.10.0/engines'},
                                                               {'source': '$MODULE/../gtk_runtime/share/icons/hicolor/', 'target': 'share/icons/hicolor'}]},
                         'pyzmq': {'module': 'zmq', 'files': [{'source': '$MODULE/libzmq.pyd', 'target': ''}]},
                        }

try:
    import pymunk
    DEFAULT_STATIC_PREFIX['pymunk'] = {'files': [{'source':
                                                  pymunk.cp.chipmunk_lib._name,
                                                  'target': ''}]}
except ImportError:
    # `pymunk` is not installed in build environment.
    pass

def walk_syspath(name_glob):
    '''Find all files matching specified pattern on executable path.

    Parameters
    ----------
    name_glob : str
        Glob file pattern to match (e.g., accepts ``*`` as wildcard).

    Yields
    ------
    path_helpers.path
        Path to file matching specified pattern found on executable path.
    '''
    for p in [ph.path(p).expand() for p in os.environ['PATH'].split(';')]:
        if p.isdir():
            for f in p.files(name_glob):
                yield f

def find_executables(name_glob):
    '''Return all distinct executables matching specified pattern.

    If multiple executables are found with the same filename, use the first
    path found in order of paths in ``$PATH`` environment variable.

    Parameters
    ----------
    name_glob : str
        Glob file pattern to match (e.g., accepts ``*`` as wildcard).

    Returns
    -------
    list[path_helpers.path]
        List of matching executable paths.
    '''
    all_matching_dlls = list(walk_syspath(name_glob))
    return zip(*sorted({f.name: f for f in reversed(all_matching_dlls)}
                       .items()))[1]

def get_package_prefix_files(package_specs, static_prefix=None):
    static_prefix = static_prefix or DEFAULT_STATIC_PREFIX
    package_specs = package_specs[:]
    package_specs.insert(0, '__core__')

    # Save shell envionment.
    init_env = os.environ.copy()

    try:
        os.environ['PY2EXE_HELPERS'] = ph.path(__file__).parent
        os.environ['SRC'] = os.getcwd()
        static_files = {}
        for package_spec_i in package_specs:
            name_i = re.split(r'=+', package_spec_i)[0]
            if name_i in static_prefix:
                package_config_i = static_prefix[name_i]
                try:
                    os.environ['MODULE'] = __import__(package_config_i
                                                      .get('module',
                                                           name_i)).__path__[0]
                except ImportError:
                    if 'MODULE' in os.environ:
                        del os.environ['MODULE']

                def _package_prefix_files(prefix_files):
                    files = []
                    for f in prefix_files:
                        source_ij = ph.path(f['source']).expand()
                        if '*' in source_ij and glob.glob(source_ij):
                            files.extend(
                                _package_prefix_files([{'source': p,
                                                        'target': f['target']}
                                                       for p in
                                                       glob.glob(source_ij)]))
                            continue
                        elif source_ij.isdir():
                            files.extend([{'source': p, 'target':
                                           ph.path(f['target'])
                                           .joinpath(source_ij
                                                     .relpathto(p.parent))
                                           .normpath()}
                                          for p in source_ij.walkfiles()])
                            continue
                        elif source_ij.isfile():
                            pass
                        elif source_ij.ext.lower() == '.dll':
                            exes_ij = find_executables(source_ij)
                            files.extend(
                                _package_prefix_files([{'source': p,
                                                        'target': f['target']}
                                                       for p in exes_ij]))
                            continue
                        elif not source_ij.isfile():
                            continue
                        files.append({'source': source_ij,
                                      'target': f['target']})
                    return files

                files = _package_prefix_files(package_config_i.get('files',
                                                                   []))
                static_files[name_i] = files
    finally:
        os.environ = init_env
    return static_files

def get_data_files(package_specs, static_prefix=None):
    static_files = get_package_prefix_files(package_specs,
                                            static_prefix=static_prefix)
    return sorted([tuple(map(str, (f['target'], f['source'])))
                   for s in static_files for f in static_files[s]])

def group_data_files(data_files):
    '''Group list of data file tuples by destination.

    Parameters
    ----------
    data_files : list[(str, str)]
        List of ``(<relative dest dir path>, <absolute source file path>)``
        items.

    Returns
    -------
    list[(str, list[str])]
        Input source file paths grouped by relative destination directory.
    '''
    return [(i, [f[1] for f in g]) for i, g in it.groupby(data_files,
                                                          lambda x: x[0])]


import numpy as np
import h5py
import sys
import os
import scipy
from scipy import integrate
import shutil
import itertools
import argparse

## Camera geodesic evaluations

def GetCameraData(p):
    """ Get data about geodesic positions and fates
        in the plane of the camera """

    file = p + '/RefinementMethodData.h5'
    f = h5py.File(file, 'r')
    data = f['LensingCore.dat']

    tags = data[:,0]
    tags = tags.astype(int)
    x_pos = data[:,1]
    y_pos = data[:,2]
    surface = data[:,4]

    return tags, x_pos, y_pos, surface

def GrabSurfaceIndices(surface, fate):

    i = np.where(surface == fate)[0]
    return i

def SelectCameraGeodesics(p, fate):

    tags, x_pos, y_pos, surface = GetCameraData(p)

    i = np.where(surface == fate)[0]

    return tags[i], x_pos[i], y_pos[i], surface[i]

def GetInfinityGeodesics(p):
    """
    inf = np.where(surface == 1.)[0]
    infty = np.where(surface == 7.)[0]
    aha = np.where(surface == 2.)[0]
    ahb = np.where(surface == 3.)[0]
    ahc = np.where(surface == 4.)[0]
    """
    file = p + '/RefinementMethodData.h5'
    f = h5py.File(file, 'r')
    data = f['LensingCore.dat']
    surface = data[:,4]
    return GrabSurfaceIndices(surface, 7)

def GetCameraPosition(p):
    p = p.split('Trace')[1]
    x = p.split('_')[1]
    y = p.split('_')[2]
    z = p.split('_')[3]
    return '[' + x + ',' + y + ',' + z + ']'

def GetTime(p):
    p = p.split('Trace')[1]
    t = p.split('_')[4]
    t = t.split('/')[0]
    return t

## Geodesic data processing

def ReadGeodesicData(p, t_start, t_end):
    """ Read in an array of times and positions for all geodesics at once,
        and return the trajectories indexed by geodesic """

    def AppendGeodesicsTime(Lev):

        print("\n Reading Geodesic data for " + Lev)

        ## Use the first segement to grab the number of geodesics
        file = p + '/' + Lev + '/Run_AA/Run/Node0.h5'
        f = h5py.File(file, 'r')
        ## grab the .dat files
        keys = [k for k in f.keys() if 'dat' in k]
        ## Array of times from the .dat files
        times = [float(k.split('.dat')[0]) for k in keys]
        ## sort keys according to times
        times, keys = zip(*sorted(zip(times, keys)))
        # grab the number of geodesics
        N_geodesics = len(f[keys[-1]][:,0])
        print("Total geodesics: ", N_geodesics, "Time steps: ", len(times))
        ## Minimum index
        m = int(sorted(f[keys[-1]][:,0])[0])
        print("Geodesic index offset of this refinement iteration: ", m)

        ## Now make the arrays

        X = [ [] for _ in range(N_geodesics)]
        Y = [ [] for _ in range(N_geodesics)]
        Z = [ [] for _ in range(N_geodesics)]
        L = [ [] for _ in range(N_geodesics)]
        T = [ [] for _ in range(N_geodesics)]

        Segments = [el for el in os.listdir(p + '/' + lev) if "Run" in el][::-1]
        print(lev + " Segments:", Segments)

        for segment in Segments:

            print("\n Working on segment " + segment)

            file = p + '/' + Lev + '/' + segment + '/Run/Node0.h5'
            f = h5py.File(file, 'r')
            ## grab the .dat files
            keys = [k for k in f.keys() if 'dat' in k]
            ## Array of times from the .dat files
            times = [float(k.split('.dat')[0]) for k in keys]
            ## sort keys according to times
            times, keys = zip(*sorted(zip(times, keys)))

            print("Going to go through the keys and the times")
            for k, t in zip(keys, times):
                ## Check that time is within the bounds
                if ((t > t_start) and (t < t_end)):
                    ## Check that time is printed at the correct interval
                    if ((t % 0.1 == 0.0) or (abs(t % 0.1 - 0.09999999999999) < 1e-9)):
                        print("%.1f  " % t, end = '', flush=True)
                        data = f[k]
                        ## indices and positions for all geodesics at this time
                        indices = data[:,0]
                        if (len(data[0]) == 7):
                            """ If we've run the numerical evolution so that we haven't
                                evolved lapse p0 -- just set lapsep0 to 1 in the files.
                                This field need to be in the resulting .dat file for the
                                paraview trajectory visualization"""
                            x = data[:,4]
                            y = data[:,5]
                            z = data[:,6]
                            l = np.ones(len(x))
                        elif (len(data[0]) == 8):
                            """ If we have evolved lapse p0"""
                            l = data[:,1]
                            x = data[:,5]
                            y = data[:,6]
                            z = data[:,7]
                        else:
                            print("Unrecognized number of variables")
                        ## fill in the array for each index
                        for i, j in zip(indices.astype(int), range(len(indices))):
                            X[i-m] = np.append(X[i-m], x[j])
                            Y[i-m] = np.append(Y[i-m], y[j])
                            Z[i-m] = np.append(Z[i-m], z[j])
                            L[i-m] = np.append(L[i-m], l[j])
                            T[i-m] = np.append(T[i-m], t)

        print("\n")
        ## Once we have the arrays constructed, append them to the file
        print('Read the geodesic data, now writing the files')

        hf = h5py.File(p + '/Trajectories.h5', 'a')
        print(p)

        for a in range(len(T)):
            if (len(T[a]) > 1):
                my_data = np.c_[T[a],X[a],Y[a],Z[a],L[a]]
                ## Remember to add in the minimum index since the starting
                ## geodesic index just gets incremented during reach refinement
                ## level (by the number of geodesics that came from the levels before)
                ## Hence a + m
                hf.create_dataset('Geodesic' + str(a+m) + '.dat', data=my_data)

        hf.close()

        print('Finished writing the files')

    ## Go through the refinement levels and the segments
    RefinementLevs = [el for el in os.listdir(p) if "Lev" in el]
    print("RefinementLevs:", RefinementLevs)
    for lev in RefinementLevs:

        AppendGeodesicsTime(lev)

def MakeGeodesicDatFiles(p, t_start, t_end):
    """ Print the result of ReadGeodesicData to files """
    ReadGeodesicData(p, t_start, t_end)

def GetGeodesicTrajectory(p, n):
    """ Read in the post-processed trajectory for the nth geodesic """
    f = p + '/Trajectories.h5'
    hf = h5py.File(f, 'r')
    data = hf['Geodesic' + str(n) + '.dat']
    t = data[:,0]
    x = data[:,1]
    y = data[:,2]
    z = data[:,3]
    hf.close()
    ## Make sure the times are sorted
    t, x, y, z = zip(*sorted(zip(t, x, y, z)))
    t = np.array(t)
    x = np.array(x)
    y = np.array(y)
    z = np.array(z)
    # Former method of reading in
    #t, x, y, z, lapse = np.loadtxt(f, comments="#",usecols=([0,1,2,3,4]),unpack=True)
    return t, x, y, z

def WriteGeodesicTrajectoryDatFile(p, n):
    """ Print the post-processed trajectory for the nth geodesic
        to a .dat file """

    t, x, y, z = GetGeodesicTrajectory(p, n)
    ## Fake lapse array
    lapse = np.ones(len(t))
    ff = open(p + '/' + str(n) + '.dat', 'w')
    np.savetxt(ff, np.c_[t,x,y,z,lapse])
    ff.close()

def GetGeodesicIndices(p, infinity=False):
    """ Return the indices of all of the geodesics we have printed to file
        If infinity == True, only returnt the geodesics that make it out to infinity """
    f = p + '/Trajectories.h5'
    hf = h5py.File(f, 'r')
    Indices = [int(k.split('Geodesic')[1].split('.dat')[0]) for k in hf.keys()]
    Indices = sorted(Indices)
    if infinity:
        indices_infinity = GetInfinityGeodesics(p)
        Indices = list(set(indices_infinity) & set(Indices))
    return Indices

## Zero crossings computations

def ComputeZeroCrossings(p, n):
    """ Compute the number of zero crossings in the y-z plane -
        useful for head-on runs where the collision happens
        along the x axis """
    t, x, y, z = GetGeodesicTrajectory(p, n)
    theta = np.arctan2(z, y)
    zero_crossings = len(np.where(np.diff(np.sign(theta)))[0])
    return zero_crossings

def MakeZeroCrossingsFile(p):
    """ Make a file with the format [geodesic index, number of zero crossings] so
        that we only have to compute the number of zero crossings once """
    ns = GetGeodesicIndices(p)
    crossings = [ComputeZeroCrossings(p, n) for n in ns]
    np.savetxt(p + 'ZeroCrossings.dat', np.c_[ns, crossings], fmt = '%d %d')

def GetGeodesicsZeroCrossings(p):
    """ Return the zero crossings for each index """
    f = p + 'ZeroCrossings.dat'
    ns, zero_crossings = np.loadtxt(f, comments="#",usecols=([0,1]),unpack=True,dtype=int)
    return ns, zero_crossings

def GetGeodesicsZeroCrossingsIndices(p, N, infinity=False):
    """ Return the indices of the geodesics that make N zero-crossings
        If infinity == True, return only the ones that make it to infinity"""
    ns, zero_crossings = GetGeodesicsZeroCrossings(p)
    indices = ns[np.where(zero_crossings == N)[0]]
    if infinity:
        indices_infinity = GetInfinityGeodesics(p)
        indices = list(set(indices_infinity) & set(indices))
    return indices

def GetGeodesicsZeroCrossingsIndicesGreater(p, N, infinity=False):
    """ Return the indices of the geodesics that make N or more zero-crossings
        If infinity == True, return only the ones that make it to infinity"""
    ns, zero_crossings = GetGeodesicsZeroCrossings(p)
    indices = ns[np.where((zero_crossings >= N))[0]]
    if infinity:
        indices_infinity = GetInfinityGeodesics(p)
        indices = list(set(indices_infinity) & set(indices))
    return indices

def GetGeodesicsMaxZeroCrossings(p, infinity=False):
    """ Return the maximum number N of zero crossings for a given run,
        along with the indices of the geodesics that make N zero-crossings
        If infinity == Ture, only return the ones that make it to infinity"""
    ns, zero_crossings = GetGeodesicsZeroCrossings(p)
    max_N = max(zero_crossings)
    indices = GetGeodesicsZeroCrossingsIndices(p, max_N, infinity=infinity)
    return max_N, indices

## X Turns computations

def ComputeXTurns(p, n):
    """ Compute tbe number of turns a geodesic makes along the x direction
        Useful for head-on runs where the collision happens along the x axis"""
    print(n, end=' ')
    t, x, y, z = GetGeodesicTrajectory(p, n)
    x_diff = x[1:] - x[:-1]
    turns = len(list(itertools.groupby(x_diff, lambda x_diff: x_diff > 0)))
    ## Set to zero turns if the trajectory does not change in x - these end up being
    ## artifical turns hovering around zero (and unresolved)
    if np.max(np.abs(x)) < 1e-3:
        turns = 0
    return turns

def MakeXTurnsFile(p):
    """ Make a file with the format [geodesic index, number of X turns] so
        that we only have to compute the number of X turns once """
    ns = GetGeodesicIndices(p)
    print('TOTAL NUMBER: ', str(len(ns)))
    turns = [ComputeXTurns(p, n) for n in ns]
    np.savetxt(p + '/XTurns.dat', np.c_[ns, turns], fmt = '%d %d')
    print('Wrote the file')

def GetGeodesicsXTurns(p):
    """ Return the x turns for each index """
    f = p + 'XTurns.dat'
    ns, turns = np.loadtxt(f, comments="#",usecols=([0,1]),unpack=True,dtype=int)
    return ns, turns

def GetGeodesicsXTurnsIndices(p, N, infinity=False):
    """ Return the indices of the geodesics that make N x turns
        If infinity == True, return only the ones that make it to infinity"""
    ns, turns = GetGeodesicsXTurns(p)
    indices = ns[np.where(turns == N)[0]]
    if infinity:
        indices_infinity = GetInfinityGeodesics(p)
        indices = list(set(indices_infinity) & set(indices))
    return indices

def GetGeodesicsXTurnsIndicesGreater(p, N, infinity=False, N_less = 1e6):
    """ Return the indices of the geodesics that make N or more x turns
        If infinity == True, return only the ones that make it to infinity"""
    ns, turns = GetGeodesicsXTurns(p)
    indices = ns[np.where(turns >= N)[0]]
    indices_less = ns[np.where(turns <= N_less)[0]]
    indices = list(set(indices) & set(indices_less))
    if infinity:
        indices_infinity = GetInfinityGeodesics(p)
        indices = list(set(indices_infinity) & set(indices))
    return indices

def GetGeodesicsMaxXTurns(p, infinity=False):
    """ Return the maximum number N of zero crossings for a given run,
        along with the indices of the geodesics that make N zero-crossings
        If infinity == Ture, only return the ones that make it to infinity"""
    f = p + 'ZeroCrossings.dat'
    ns, turns = GetGeodesicsXTurns(p)
    max_N = max(turns)
    indices = GetGeodesicsXTurnsIndices(p, max_N, infinity=infinity)
    return max_N, indices

## Frenet-Serret computations

def ComputeArcLength(t, x, y, z):
    """ For a given trajectory {x(t), y(t), z(t)}, compute and return
        the arclength s(t) as a function of time"""
    dx = np.gradient(x, t)
    dy = np.gradient(y, t)
    dz = np.gradient(z, t)

    ## Compute the magnitude
    mag = np.sqrt(dx**2 + dy**2 + dz**2)

    ## Take a running integral
    s = integrate.cumtrapz(mag, t, initial = 0.0)

    ## check that s(t) is monotonically increasing
    check = np.where((s[1:] - s[:-1]) <= 0.0)[0]
    if len(check) > 0:
        print("s(t) is non-monotonically increasing!")

    return s

def ComputeFrenetSerretTNB(t, x, y, z):
    s = ComputeArcLength(t, x, y, z)

    T_x = np.gradient(x, s)
    T_y = np.gradient(y, s)
    T_z = np.gradient(z, s)

    T_mag = np.sqrt(T_x**2 + T_y**2 + T_z**2)

    T_x = T_x / T_mag
    T_y = T_y / T_mag
    T_z = T_z / T_mag

    dTds_x = np.gradient(T_x, s)
    dTds_y = np.gradient(T_y, s)
    dTds_z = np.gradient(T_z, s)

    dTds_mag = np.sqrt(dTds_x**2 + dTds_y**2 + dTds_z**2)

    N_x = dTds_x / dTds_mag
    N_y = dTds_y / dTds_mag
    N_z = dTds_z / dTds_mag

    B_x = T_y * N_z - T_z * N_y
    B_y = - T_x * N_z + T_z * N_x
    B_z = T_x * N_y - T_y * N_x

    kappa = dTds_mag ## curvature

    return T_x, T_y, T_z, N_x, N_y, N_z, B_x, B_y, B_z, kappa

def MakeFrenetSerretDatFiles(p):
    """ For all geodesics .dat files in a given directory, dump the Frenet-Serret Frame
        data (so we only have to compute it once)"""
    ns = GetGeodesicIndices(p)
    for n in ns:
        t, x, y, z = GetGeodesicTrajectory(p, n)
        t, x, y, z = zip(*sorted(zip(t, x, y, z)))
        T_x, T_y, T_z, N_x, N_y, N_z, B_x, B_y, B_z, kappa = ComputeFrenetSerretTNB(t, x, y, z)
        np.savetxt(p + '/FrenetSerret/' + str(n) + '.dat', np.c_[t, kappa, T_x, T_y, T_z, \
                                                                        N_x, N_y, N_z, \
                                                                        B_x, B_y, B_z])

def GetFrenetSerretVectors(p, n):
    """ Read in the post-processed trajectory for the nth geodesic """
    f = p + '/FrenetSerret/' + str(n) + '.dat'
    t, kappa, T_x, T_y, T_z, N_x, N_y, N_z, B_x, B_y, B_z \
        = np.loadtxt(f, comments="#",usecols=(range(11)),unpack=True)
    return t, kappa, T_x, T_y, T_z, N_x, N_y, N_z, B_x, B_y, B_z

def GetFrenetSerretCurvature(p, n):
    """ Read in the post-processed curvature for the nth geodesic """
    f = p + '/FrenetSerret/' + str(n) + '.dat'
    t, kappa = np.loadtxt(f, comments="#",usecols=([0,1]),unpack=True)
    return t, kappa

def MakeMaxCurvatureFile(p):
    """ Make file constraining the geodesic ID, the maximum value of the curvature,
        and the x, y, z, coordinates of this point"""
    ns = GetGeodesicIndices(p)
    N = len(ns)
    MaxKappa = np.zeros(N)
    T = np.zeros(N)
    X = np.zeros(N)
    Y = np.zeros(N)
    Z = np.zeros(N)

    for i in range(N):
        n = ns[i]
        t, x, y, z = GetGeodesicTrajectory(p, n)
        time, kappa = GetFrenetSerretCurvature(p, n)
        index = np.argmax(kappa)
        MaxKappa[i] = kappa[index]
        T[i] = t[index]
        X[i] = x[index]
        Y[i] = y[index]
        Z[i] = z[index]

    np.savetxt(p + '/MaxCurvatures.dat', np.c_[ns, MaxKappa, T, X, Y, Z], fmt = '%d %f %f %f %f %f')

def main():

    p = argparse.ArgumentParser(description="Post-process geodesics evolution data")
    p.add_argument("--dir", required=True, \
           help="Root directory of geodesic evolution data to post-process")
    p.add_argument("--trajectories", help='Post-process the trajectories', \
        dest='trajectories', action='store_true')
    p.add_argument("--zerocrossings", help='Post-process the zero-crossings', \
        dest='zerocrossings', action='store_true')
    p.add_argument("--xturns", help='Post-process the x-turns', \
        dest='xturns', action='store_true')
    p.add_argument("--frenetserret", help='Perform the Frenet-Serret analysis', \
        dest='frenetserret', action='store_true')

    p.set_defaults(trajectories=False)
    p.set_defaults(zerocrossings=False)
    p.set_defaults(xturns=False)
    p.set_defaults(frenetserret=False)

    args = p.parse_args()

    print("Processing geodesics for " + args.dir)

    ## Make geodesic dat files
    if (args.trajectories):
        print("Making the geodesic trajectory file")
        try:
            os.remove(args.dir + '/Trajectories.h5')
            print("Removed previous Trajectories.h5 file")
        except:
            print("Trajectories.h5 file did not previously exist")
        MakeGeodesicDatFiles(args.dir, 0, 10000)

    ## Compute zero crossings
    if (args.zerocrossings):
        print("Computing zero crossings")
        MakeZeroCrossingsFile(args.dir)

    ## Compute x turns
    if (args.xturns):
        print("Computing x turns")
        MakeXTurnsFile(args.dir)

    ## Compute Frenet-Serret
    if (args.frenetserret):
        print("Computing Frenet-Serret")
        try:
            os.mkdir(p + '/FrenetSerret')
        except:
            print("FrenetSerret directory already exists")
        MakeFrenetSerretDatFiles(p)
        print("Computing max curvatures")
        MakeMaxCurvatureFile(p)

if __name__ == "__main__":
    main()


from flask import Flask, render_template, request
from MachineLearningCode import MachineLearningCode

# apps initializations
app = Flask(__name__)
print("Flask app initialized")
MLC = MachineLearningCode()

# routes definitions

@app.route('/')
@app.route('/home')
def hello_world():
    print("Hello World!")
    return render_template("index.html")

@app.route('/predict', methods=['POST'])
def predictandoutput():
    text1 = request.form["text1"]
    text2 = request.form["text2"]

    Result = MLC.predict(text1,text2)
    return render_template("result.html", similarity_score=Result)

# app run
if __name__ == '__main__':
    app.run(debug=True, port=5001)


from web3 import Web3
from telebot import TeleBot, types
import config, helper, json
from web3._utils.events import EventLogErrorFlags
from apscheduler.schedulers.background import BackgroundScheduler

bot = TeleBot(config.api_token)
web3 = Web3(Web3.HTTPProvider(config.rpc_url))
schd = BackgroundScheduler(daemon=True)
class myFilter: rpcFilter = web3.eth.filter({
#    'address': config.contract_address,
    'topics': [config.sig_to_track],
})

def handleTX(x, ca):
    allTX = helper.getAllTX()
    if x.lower() in allTX: return print('Tx already exists')
    logsR = web3.eth.getTransactionReceipt(x)
    with open('./token.json', 'r') as file: abiData = json.load(file)

    txInfo = web3.eth.getTransaction(x)
    print(getattr(txInfo, "from"), getattr(txInfo, "to"))

    contract = web3.eth.contract(address=web3.toChecksumAddress(ca), abi=abiData)

    logs = contract.events.SettledRPSLSvsChainlink().processReceipt(logsR, errors=EventLogErrorFlags.Discard)
    if len(logs) == 0: return print('No Valid Event Found')

    validData = logs[0].args
    print(validData)
    wagererAddress = validData.wagerer
    amountWagerer = validData.amountWagered / 10 ** 18
    hand = validData.hand
    chainLink = validData.chainlinkPlayed
    resultMatch = validData.result

    markup = types.InlineKeyboardMarkup()
    btn = types.InlineKeyboardButton(text="Transaction Hash", url="https://etherscan.io/tx/"+x)
    markup.add(btn)

    player_stats = contract.functions.playerStats(wagererAddress).call()
    print(logsR)
    wins = player_stats[0]
    loses = player_stats[2]
    draws = player_stats[4]

    amountWin = 0 if player_stats[1] == 0 else player_stats[1] / 10 ** 18
    amountLoss = 0 if player_stats[3] == 0 else player_stats[3] / 10 ** 18

    text_to_send = f"""Round : {wagererAddress[0:5] + '...'} wagered {helper.roundToNearestZero(amountWagerer)} $BBT and {helper.getResult(resultMatch)} with {helper.getHandWithNumber(hand)}!

Player Stats:
🐸Wins:{wins}  🛑Loses: {loses} ✏Draws: {draws}
BBT WON: ({int(amountWin)} LOSS: {int(amountLoss)})
"""

    video_link = open("./YouWon.mp4", "rb") if helper.getResult(resultMatch) == 'won' else open("./YouLost.mp4", "rb")

    bot.send_video(
        video=video_link,
        caption=text_to_send,
        chat_id="-1001763761758",
        reply_markup=markup,
        parse_mode= "HTML"
    )

    # bot.send_photo(
    #     chat_id="-1001763761758",
    #     caption=text_to_send,
    #     parse_mode="HTML",
    #     reply_markup=markup,
    #     # disable_web_page_preview=True,
    #     photo=""" # )
    helper.insertTX(x.lower())

def checkTokenBuys():
    try:
       allEntries = myFilter.rpcFilter.get_new_entries()
    except:
       myFilter.rpcFilter = web3.eth.filter({ 'topics': [config.sig_to_track] })
       return print("filter broken")
    allEntries = helper.removeDuplicateEntries(allEntries)
    print(allEntries)

    for x in allEntries:
        handleTX(x.transactionHash.hex(), "0x0c4A49567Cf002A99941cb19C973E794c04B8c30") #x.address

schd.add_job(checkTokenBuys, 'interval', seconds=5)
schd.start()
while True: pass

# result = myFilter.rpcFilter.get_all_entries()
# result = helper.removeDuplicateEntries(result)
# print(result)


from django import forms
from .models import Group
from socle.models import Subject,  Level

class GroupForm(forms.ModelForm):

    def __init__(self, *args, **kwargs):
        super(GroupForm, self).__init__(*args, **kwargs)
        subjects = Subject.objects.filter(is_active=1)
        self.fields['subject']  = forms.ModelChoiceField(queryset=subjects)
        levels = Level.objects.order_by("ranking")
        self.fields['level']  = forms.ModelChoiceField(queryset=levels)

    class Meta:
        model = Group
        fields = ('name','labelname','color','level','is_hidden','assign','suiviparent','lock','subject','studentprofile' )

class GroupTeacherForm(forms.ModelForm):
	class Meta:
		model = Group
		fields = ('name','labelname','color','level','assign','is_hidden','suiviparent','lock','subject','studentprofile','recuperation')

	def __init__(self, *args, **kwargs):
		teacher = kwargs.pop('teacher')
		super(GroupTeacherForm, self).__init__(*args, **kwargs)
		if teacher:

			# subjects = teacher.subjects.all()
			# levels = teacher.levels.order_by("ranking")

			# if subjects :
			# 	self.fields['subject']	 = forms.ModelChoiceField(queryset=subjects,  required=True)
			# else :
			subjects = Subject.objects.filter(is_active=1)
			self.fields['subject']	 = forms.ModelChoiceField(queryset=subjects,  required=True)

			# if levels :
			# 	self.fields['level']	 = forms.ModelChoiceField(queryset=levels,  required=True)
			# else :
			subjects = Level.objects.exclude(pk=13).order_by("ranking")
			self.fields['level']	 = forms.ModelChoiceField(queryset=subjects,  required=True)


"""
-*- coding: utf-8 -*-

@Time    : 2021/10/21 10:03

@Author  : Wang Ziming

@Email   : zi_ming_wang@outlook.com

@File    : snn.py

Reference:
    1. Gu, Pengjie, et al. "STCA: Spatio-Temporal Credit Assignment with Delayed Feedback in Deep Spiking Neural Networks." IJCAI. 2019.
    2. Zimmer, Romain, et al. "Technical report: supervised training of convolutional spiking neural networks with PyTorch." arXiv preprint arXiv:1911.10124 (2019).
    3. 加速自定义RNN Cell: https://github.com/pytorch/pytorch/blob/963f7629b591dc9750476faf1513bc7f1fb4d6de/benchmarks/fastrnns/custom_lstms.py#L246
"""
import torch
import numpy as np
from .function import td_Dropout

class SNN(torch.nn.Module):

    def __init__(self, layers):

        super(SNN, self).__init__()
        self.layers = torch.nn.ModuleList(layers)

    def forward(self, x):
        loss_seq = []
        for l in self.layers:
            # if isinstance(l, tdBatchNorm):
            #     x = x.unsqueeze(1)
            #     x = x.unsqueeze(-1)
            #     # print(x.shape)
            #     x = l(x)
            #     x = x.squeeze()
            # else:
                x, loss = l(x)
                loss_seq.append(loss)

        return x, loss_seq

    def clamp(self):

        for l in self.layers:
                l.clamp()

    def reset_parameters(self):

        for l in self.layers:
                l.reset_parameters()

class STCADenseLayer(torch.nn.Module):

    def __init__(self, input_shape, output_shape, spike_fn, w_init_mean, w_init_std,
                 recurrent=False, lateral_connections=True, rc_drop=-1, fc_drop=-1, eps=1e-8):

        super(STCADenseLayer, self).__init__()

        self.input_shape = input_shape
        self.output_shape = output_shape
        self.spike_fn = spike_fn
        self.recurrent = recurrent
        self.eps = eps
        self.lateral_connections = lateral_connections

        self.w_init_mean = w_init_mean
        self.w_init_std = w_init_std

        self.w = torch.nn.Parameter(torch.empty((input_shape, output_shape)), requires_grad=True)
        if recurrent:
            self.v = torch.nn.Parameter(torch.empty((output_shape, output_shape)), requires_grad=True)

        # RNN Dropout
        self.fc_drop = fc_drop
        self.rc_drop = rc_drop
        if fc_drop > 0:
            self.drop_fc = td_Dropout(fc_drop, True)
        if rc_drop > 0:
            self.drop_rc = td_Dropout(rc_drop, True)

        self.b = torch.nn.Parameter(torch.empty(output_shape), requires_grad=True)  # threshhold

        # decay
        self.decay_m = torch.nn.Parameter(torch.empty(1), requires_grad=True)
        self.decay_s = torch.nn.Parameter(torch.empty(1), requires_grad=True)
        self.decay_e = torch.nn.Parameter(torch.empty(1), requires_grad=True)

        self.reset_parameters()
        self.clamp()

        self.spk_rec_hist = None

        self.training = True

    def forward(self, x):

        batch_size = x.shape[0]
        # update drop out mask
        if self.fc_drop > 0:
            self.drop_fc.reset()
        if self.rc_drop > 0:
            self.drop_rc.reset()
        # todo: 爱因斯坦求和标记，支持torch BP
        h = torch.einsum("abc,cd->abd", x, self.w)
        nb_steps = h.shape[1]

        # output spikes
        spk = torch.zeros((batch_size, self.output_shape), dtype=x.dtype, device=x.device)
        # refractory period kernel
        E = torch.zeros((batch_size, self.output_shape), dtype=x.dtype, device=x.device)
        # input response kernel
        M = torch.zeros((batch_size, self.output_shape), dtype=x.dtype, device=x.device)
        S = torch.zeros((batch_size, self.output_shape), dtype=x.dtype, device=x.device)

        # output spikes recording
        spk_rec = torch.zeros((batch_size, nb_steps, self.output_shape), dtype=x.dtype, device=x.device)

        if self.lateral_connections:  # 计算侧抑制权重
            d = torch.einsum("ab, ac -> bc", self.w, self.w)

        norm = (self.w ** 2).sum(0)  # 对每个输出神经元计算norm以约束权重

        for t in range(nb_steps):
            # input term
            input_ = h[:, t, :]
            if self.recurrent:
                if self.rc_drop > 0:
                    input_ = input_ + torch.einsum("ab,bc->ac", self.drop_rc(spk), self.v)
                else:
                    input_ = input_ + torch.einsum("ab,bc->ac", spk, self.v)

            # todo: add reset mechanism
            if self.lateral_connections:
                # 模拟侧抑制
                E = torch.einsum("ab,bc ->ac", spk, d)
            else:
                # 模拟refractory period
                E = self.decay_m * norm * (E + spk)

            M = self.decay_m * (M + input_)
            S = self.decay_s * (S + input_)

            # membrane potential update
            mem = M - S - E
            mthr = torch.einsum("ab,b->ab", mem, 1. / (norm + self.eps)) - self.b

            spk = self.spike_fn(mthr)
            spk_rec[:, t, :] = spk
            if self.fc_drop > 0:
                spk = self.drop_fc(spk)

            # save spk_rec for plotting
        self.spk_rec_hist = spk_rec.detach().cpu().numpy()  # 记录该层的脉冲发放 shape: batch_size x T x NeuIn

        loss = 0.5 * (spk_rec ** 2).mean()

        return spk_rec, loss

    # todo: 将权重的初始化逻辑从module中独立出来形成Initilizer
    def reset_parameters(self):

        torch.nn.init.normal_(self.w, mean=self.w_init_mean, std=self.w_init_std * np.sqrt(1. / self.input_shape))
        if self.recurrent:
            torch.nn.init.normal_(self.v, mean=self.w_init_mean, std=self.w_init_std * np.sqrt(1. / self.output_shape))
        torch.nn.init.normal_(self.decay_m, mean=0.6, std=0.01)
        torch.nn.init.normal_(self.decay_s, mean=0.9, std=0.01)
        torch.nn.init.normal_(self.decay_e, mean=0.9, std=0.01)
        torch.nn.init.normal_(self.b, mean=1., std=0.01)

    def reset_parameters_v2(self):
        torch.nn.init.normal_(self.w, mean=self.w_init_mean, std=self.w_init_std * np.sqrt(1. / self.input_shape))
        if self.recurrent:
            torch.nn.init.orthogonal(self.v)
        torch.nn.init.normal_(self.decay_m, mean=0.6, std=0.01)
        torch.nn.init.normal_(self.decay_s, mean=0.9, std=0.01)
        torch.nn.init.normal_(self.decay_e, mean=0.9, std=0.01)
        torch.nn.init.normal_(self.b, mean=1., std=0.01)

    def reset_parameters_v3(self):
        torch.nn.init.orthogonal(self.w)
        if self.recurrent:
            torch.nn.init.orthogonal(self.v)
        torch.nn.init.normal_(self.decay_m, mean=0.6, std=0.01)
        torch.nn.init.normal_(self.decay_s, mean=0.9, std=0.01)
        torch.nn.init.normal_(self.decay_e, mean=0.9, std=0.01)
        torch.nn.init.normal_(self.b, mean=1., std=0.01)

    def reset_parameters_v4(self):
        torch.nn.init.xavier_normal_(self.w)
        if self.recurrent:
            torch.nn.init.xavier_normal_(self.v)
        torch.nn.init.normal_(self.decay_m, mean=0.6, std=0.01)
        torch.nn.init.normal_(self.decay_s, mean=0.9, std=0.01)
        torch.nn.init.normal_(self.decay_e, mean=0.9, std=0.01)
        torch.nn.init.normal_(self.b, mean=1., std=0.01)

    # todo: 将decay改为可学习的softmax门控机制
    def clamp(self):
        self.decay_m.data.clamp_(0., 1.)
        self.decay_e.data.clamp_(0., 1.)
        self.decay_s.data.clamp_(0., 1.)
        self.b.data.clamp_(min=0.)

class ReadoutLayer(torch.nn.Module):
    "Fully connected readout"

    def __init__(self, input_shape, output_shape, w_init_mean, w_init_std, eps=1e-8, time_reduction="mean"):

        assert time_reduction in ["mean", "max"], 'time_reduction should be "mean" or "max"'

        super(ReadoutLayer, self).__init__()

        self.input_shape = input_shape
        self.output_shape = output_shape

        self.w_init_mean = w_init_mean
        self.w_init_std = w_init_std

        self.eps = eps
        self.time_reduction = time_reduction

        self.w = torch.nn.Parameter(torch.empty((input_shape, output_shape)), requires_grad=True)
        if time_reduction == "max":
            self.beta = torch.nn.Parameter(torch.tensor(0.7 * np.ones((1))), requires_grad=True)
        self.b = torch.nn.Parameter(torch.empty(output_shape), requires_grad=True)

        self.reset_parameters()
        self.clamp()

        self.mem_rec_hist = None

    def forward(self, x):

        batch_size = x.shape[0]

        h = torch.einsum("abc,cd->abd", x, self.w)

        norm = (self.w ** 2).sum(0)

        if self.time_reduction == "max":
            nb_steps = x.shape[1]
            # membrane potential
            mem = torch.zeros((batch_size, self.output_shape), dtype=x.dtype, device=x.device)

            # memrane potential recording
            mem_rec = torch.zeros((batch_size, nb_steps, self.output_shape), dtype=x.dtype, device=x.device)

            for t in range(nb_steps):
                # membrane potential update
                mem = mem * self.beta + (1 - self.beta) * h[:, t, :]
                mem_rec[:, t, :] = mem

            output = torch.max(mem_rec, 1)[0] / (norm + 1e-8) - self.b

        elif self.time_reduction == "mean":

            mem_rec = h
            output = torch.mean(mem_rec, 1) / (norm + 1e-8) - self.b

        # save mem_rec for plotting
        self.mem_rec_hist = mem_rec.detach().cpu().numpy()

        loss = None

        return output, loss

    def reset_parameters(self):
        torch.nn.init.normal_(self.w, mean=self.w_init_mean,
                              std=self.w_init_std * np.sqrt(1. / (self.input_shape)))

        if self.time_reduction == "max":
            torch.nn.init.normal_(self.beta, mean=0.7, std=0.01)

        torch.nn.init.normal_(self.b, mean=0., std=0.01)

    def reset_parameters_v4(self):
        torch.nn.init.xavier_normal_(self.w)

        if self.time_reduction == "max":
            torch.nn.init.normal_(self.beta, mean=0.7, std=0.01)

        torch.nn.init.normal_(self.b, mean=1., std=0.01)

    def clamp(self):

        if self.time_reduction == "max":
            self.beta.data.clamp_(0., 1.)