#!/usr/bin/env python

import cv2
import sys
import copy
import rospy
from std_msgs.msg import String
import depthai
import platform
import signal
import subprocess
import json
import datetime

from calibration.srv import Capture
import time
import numpy as np
import os
from pathlib import Path
import shutil
import consts.resource_paths
from cv_bridge import CvBridge
from sensor_msgs.msg import Image
import shutil
from depthai_helpers.calibration_utils import *

from depthai_helpers.pygame_checkbox import Checkbox, pygame_render_text
import pygame
from pygame.locals import *

from depthai_helpers import utils
os.environ['SDL_VIDEO_WINDOW_POS'] = '100,50'

on_embedded = platform.machine().startswith('arm') or platform.machine().startswith('aarch64')

def find_chessboard(frame):
    chessboard_flags = cv2.CALIB_CB_ADAPTIVE_THRESH + cv2.CALIB_CB_FAST_CHECK + cv2.CALIB_CB_NORMALIZE_IMAGE
    small_frame = cv2.resize(frame, (0, 0), fx=0.3, fy=0.3)
    return cv2.findChessboardCorners(small_frame, (9, 6), chessboard_flags)[0] and \
           cv2.findChessboardCorners(frame, (9, 6), chessboard_flags)[0]

white  = [255, 255, 255]
orange = [143, 122, 4]
red    = [230, 9, 9]
green  = [4, 143, 7]
black  = [0, 0, 0]
pygame.init()

class depthai_calibration_node:
    def __init__(self, depthai_args):
        self.package_path = depthai_args['package_path']
        self.args = depthai_args
        self.bridge = CvBridge()
        self.is_service_active = False
        self.config = {
            'streams':
                ['left', 'right', 'meta_d2h', 'color'] if not on_embedded else
                [{'name': 'left', "max_fps": 30.0}, {'name': 'right', "max_fps": 30.0}, {'name': 'meta_d2h', "max_fps": 30.0}, {'name': 'color', "max_fps": 30.0}],
            'depth':
                {
                    'calibration_file': consts.resource_paths.calib_fpath,
                    'padding_factor': 0.3
                },
            'ai':
                {
                    'blob_file': consts.resource_paths.blob_fpath,
                    'blob_file_config': consts.resource_paths.blob_config_fpath,
                    'shaves' : 7,
                    'cmx_slices' : 7,
                    'NN_engines' : 1,
                },
            'board_config':
                {
                    'swap_left_and_right_cameras': self.args['swap_lr'],
                    'left_fov_deg':  self.args['field_of_view'],
                    'left_to_right_distance_cm': self.args['baseline'],
                    'override_eeprom': False,
                    'stereo_center_crop': True,
                },
            'camera':
                {
                    'mono':
                    {
                        # 1280x720, 1280x800, 640x400 (binning enabled)
                        'resolution_h': 800,
                        'fps': 30.0,
                    },
                },
        }

        if arg['board']:
            board_path = Path(arg['board'])
            if not board_path.exists():
                board_path = Path(consts.resource_paths.boards_dir_path) / Path(arg['board'].upper()).with_suffix('.json')
                print(board_path)
                if not board_path.exists():
                    raise ValueError('Board config not found: {}'.format(board_path))
            with open(board_path) as fp:
                self.board_config = json.load(fp)
        utils.merge(self.board_config, self.config)

        self.aruco_dictionary = cv2.aruco.Dictionary_get(cv2.aruco.DICT_4X4_1000)
        # self.charuco_board = aruco.CharucoBoard_create(
        #                                     22,16,
        #                                     self.args['square_size_cm'],
        #                                     self.args['marker_size_cm'],
        #                                     self.aruco_dictionary)
        self.start_device()

        self.disp = pygame.display
        self.screen = self.disp.set_mode((800, 600))
        self.screen.fill(white)
        self.disp.set_caption("Calibration - Device check ")
        title = "Device Status"
        pygame_render_text(self.screen, title, (350,20), orange, 50)
        self.auto_checkbox_names = ["USB3", "Left camera connected", "Right camera connected",
                                    "Left Stream", "Right Stream"]
        if self.args['enable_IMU_test']:
            self.auto_checkbox_names.append("IMU connected")

        y = 110
        x = 200
        self.start_disp = False
        font = pygame.font.Font(None, 20)
        self.auto_checkbox_dict = {}
        for i in range(len(self.auto_checkbox_names)):
            w, h = font.size(self.auto_checkbox_names[i])
            x_axis = x - w
            y_axis = y +  (40*i)
            font_surf = font.render(self.auto_checkbox_names[i], True, green)
            self.screen.blit(font_surf, (x_axis,y_axis))
            self.auto_checkbox_dict[self.auto_checkbox_names[i]] = Checkbox(self.screen, x + 10, y_axis-5, outline_color=green,
                                                        check_color=green, check=False)

        # text = 'call rosservice of device_status_handler to update the device status'
        for i in range(len(self.auto_checkbox_names)):
            self.auto_checkbox_dict[self.auto_checkbox_names[i]].render_checkbox()
        pygame.draw.rect(self.screen, red, no_button)
        pygame_render_text(self.screen, 'Exit', (500, 505))
        self.no_active = False
        self.click = False
        # self.disp.update()
        ## creating services and publishers at the end to avoid calls before initialization
        self.capture_srv = rospy.Service(self.args["capture_service_name"], Capture, self.capture_servive_handler)
        self.calib_srv = rospy.Service(self.args["calibration_service_name"], Capture, self.calibration_servive_handler)
        self.dev_status_srv = rospy.Service("device_status", Capture, self.device_status_handler)
        self.image_pub_left = rospy.Publisher("left",Image, queue_size=10)
        self.image_pub_right = rospy.Publisher("right",Image, queue_size=10)

    def capture_exit(self):
        is_clicked = False
        for event in pygame.event.get():
            # self.disp.update()
            if event.type == pygame.MOUSEMOTION:
                x, y = event.pos
                px, py, w, h = no_button
                if px < x < px + w and py < y < py + h:
                    self.no_active = True
                    pygame.draw.rect(self.screen, orange, no_button)
                    pygame_render_text(self.screen, 'Exit', (500, 505))
                else:
                    self.no_active = False
                    pygame.draw.rect(self.screen, red, no_button)
                    pygame_render_text(self.screen, 'Exit', (500, 505))

            if event.type == pygame.MOUSEBUTTONDOWN and self.no_active:
                print("setting click")
                self.click = True
            if event.type == pygame.MOUSEBUTTONUP:
                if self.no_active and self.click:
                    print("No clicked")
                    is_clicked = True
                    # is_device_ready = False
                    # if arm_control is not None:
                        # arm_control.go_to_next_joint_state(joints_goal_dict['sleep'])
                    break
        # pygame.event.pump()
        return is_clicked

    def start_device(self):
        self.device = depthai.Device('', False)
        # self.device = depthai.Device('/home/nuc/Desktop/depthai/.fw_cache/depthai-6fc8c54e33b8aa6d16bf70ac5193d10090dcd0d8.cmd', '')
        self.pipeline = self.device.create_pipeline(self.config)
        self.mx_id = self.device.get_mx_id()

    def publisher(self):
        while not rospy.is_shutdown():
            if self.capture_exit():
                print("signaling...")
                rospy.signal_shutdown("Finished calibration")
            if self.start_disp:
                self.disp.update()
            # else:
            #     pygameX.event.pump()
            # print("updating dis-----")
            if not self.is_service_active:
                # pygame.draw.rect(self.screen, red, no_button)
                # pygame_render_text(self.screen, 'Exit', (500, 505))
                # print("SERVICE NOT ACTIVE")
                if not hasattr(self, "pipeline"):
                    self.start_device()
                    # print("restarting device---->")
                _, data_list = self.pipeline.get_available_nnet_and_data_packets(True)
                for packet in data_list:
                    # print("found packets:")
                    # print(packet.stream_name)
                    if packet.stream_name == "left":
                        recent_left = packet.getData()
                        # print(recent_left.shape)
                        self.image_pub_left.publish(self.bridge.cv2_to_imgmsg(recent_left, "passthrough"))
                    elif packet.stream_name == "right":
                        recent_right = packet.getData()
                        self.image_pub_right.publish(self.bridge.cv2_to_imgmsg(recent_right, "passthrough"))
                    elif packet.stream_name == "meta_d2h":
                        str_ = packet.getDataAsStr()

    def parse_frame(self, frame, stream_name, file_name):
        file_name += '.png'
        # filename = image_filename(stream_name, self.current_polygon, self.images_captured)
        # print(self.package_path + "/dataset/{}/{}".format(stream_name, file_name))
        ds_path = self.package_path + "/dataset/{}".format(stream_name)
        # print(ds_path)
        if not os.path.exists(ds_path):
            os.makedirs(ds_path)

        cv2.imwrite(self.package_path + "/dataset/{}/{}".format(stream_name, file_name), frame)
        print("py: Saved image as: " + str(file_name))
        return True

    def is_markers_found(self, frame):
        # print(frame.shape)
        # gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        marker_corners, _, _ = cv2.aruco.detectMarkers(frame, self.aruco_dictionary)
        return not (len(marker_corners) == 0)

    def capture_servive_handler(self, req):
        print("Capture image Service Started")
        recent_left = None
        recent_right = None
        finished = False
        self.is_service_active = True
        # now = rospy.get_rostime()
        # pygame.draw.rect(self.screen, white, no_button)
        while not finished:
            _, data_list = self.pipeline.get_available_nnet_and_data_packets()
            # print(len(data_list))

            for packet in data_list:
                # print(packet.stream_name)
                # print("packet time")
                # print(packet.getMetadata().getTimestamp())
                # print("ros time")
                # print(now.secs)
                if packet.stream_name == "left":
                    recent_left = packet.getData()
                elif packet.stream_name == "right":
                    recent_right = packet.getData()

            if recent_left is not None and recent_right is not None:
                finished = True

        # print("looping")
        # is_board_found_l = find_chessboard(recent_left)
        # is_board_found_r = find_chessboard(recent_right)
        # is_board_found_l = True
        # is_board_found_r = True
        is_board_found_l = self.is_markers_found(recent_left)
        is_board_found_r = self.is_markers_found(recent_right)
        if is_board_found_l and is_board_found_r:
            print("Found------------------------->")
            self.parse_frame(recent_left, "left", req.name)
            self.parse_frame(recent_right, "right", req.name)
        else:
            print("Not found--------------------->")
            self.is_service_active = False
            self.parse_frame(recent_left, "left_not", req.name)
            self.parse_frame(recent_right, "right_not", req.name)
            return (False, "Calibration board not found")
        # elif is_board_found_l and not is_board_found_r: ## TODO: Add errors after srv is built
        print("Service ending")
        self.is_service_active = False
        return (True, "No Error")

    def device_status_handler(self, req):
        self.is_service_active = True
        self.start_disp = True
        # pygame.draw.rect(self.screen, white, no_button)
        while not self.device.is_device_changed():
            text = "Waiting for device change"
            pygame_render_text(self.screen, text, (250,400), orange, 40)

        # to remove waiting for device change
        fill_color =  pygame.Rect(200, 400, 450, 55)
        pygame.draw.rect(self.screen, white, fill_color)

        # to remove previous date and stuff
        fill_color =  pygame.Rect(400, 70, 500, 150)
        pygame.draw.rect(self.screen, white, fill_color)
        now_time = datetime.datetime.now()
        text = "date/time : " + now_time.strftime("%m-%d-%Y %H:%M:%S")
        pygame_render_text(self.screen, text, (400,80), black, 30)
        text = "device Mx_id : " + self.device.get_mx_id()
        pygame_render_text(self.screen, text, (400,120), black, 30)
        rospy.sleep(3)
        fill_color_2 =  pygame.Rect(50, 520, 400, 80)
        pygame.draw.rect(self.screen, white, fill_color_2)
        while self.device.is_device_changed():
            # print(self.device.is_device_changed())
            # if self.capture_exit():
            #     print("signaling...")
            #     rospy.signal_shutdown("Finished calibration")
            is_usb3 = False
            left_mipi = False
            right_mipi = False
            if self.args['enable_IMU_test']:
                is_IMU_connected = False
            else:
                is_IMU_connected = True
            is_usb3 = self.device.is_usb3()
            left_status = self.device.is_left_connected()
            right_status = self.device.is_right_connected()

            imu_times = 0
            if self.capture_exit():
                rospy.signal_shutdown("Finished calibration")

            # else
            if left_status and right_status:
                # mipi check using 20 iterations
                # ["USB3", "Left camera connected", "Right camera connected", "left Stream", "right Stream"]
                for _ in range(90):
                    _, data_list = self.pipeline.get_available_nnet_and_data_packets(True)
                    # print(len(data_list))
                    for packet in data_list:
                        # print("found packets:")
                        # print(packet.stream_name)
                        if packet.stream_name == "left":
                            recent_left = packet.getData()
                            left_mipi = True
                            self.image_pub_left.publish(self.bridge.cv2_to_imgmsg(recent_left, "passthrough"))
                        elif packet.stream_name == "right":
                            recent_right = packet.getData()
                            right_mipi = True
                            self.image_pub_right.publish(self.bridge.cv2_to_imgmsg(recent_right, "passthrough"))
                        elif packet.stream_name == "meta_d2h":
                            str_ = packet.getDataAsStr()
                            if not self.args['enable_IMU_test']:
                                continue
                            dict_ = json.loads(str_)
                            if 'imu' in dict_:
                                if imu_times >= 5:
                                    is_IMU_connected = True
                                fill_color =  pygame.Rect(50, 500, 400, 100)
                                pygame.draw.rect(self.screen, white, fill_color)
                                selected_clr = red

                                if dict_['imu']['status'] == 'IMU init FAILED':
                                    selected_clr = red
                                else:
                                    imu_times += 1
                                    selected_clr = green
                                    text = 'IMU acc x: {:7.4f}  y:{:7.4f}  z:{:7.4f}'.format(dict_['imu']['accel']['x'], dict_['imu']['accel']['y'], dict_['imu']['accel']['z'])
                                    pygame_render_text(self.screen, text, (50, 545), font_size=25)
                                    text = 'IMU acc-raw x: {:7.4f}  y:{:7.4f}  z:{:7.4f}'.format(dict_['imu']['accelRaw']['x'], dict_['imu']['accelRaw']['y'], dict_['imu']['accelRaw']['z'])
                                    pygame_render_text(self.screen, text, (50, 570), font_size=25)

                                text = 'IMU status: ' + dict_['imu']['status']
                                pygame_render_text(self.screen, text, (50, 500), font_size=30, color=selected_clr)

                            # print(dict_)
                            # if 'logs' in dict_:
                            #     for log in dict_['logs']:
                            #         if log != 'IMU init FAILED':
                            #             text = 'IMU status: ' + log
                            #             is_IMU_connected = True
                            #             pygame_render_text(self.screen, text, (50, 500), font_size=25, color=green)
                            #         else:
                            #             text = 'IMU status: ' + log
                            #             pygame_render_text(self.screen, text, (50, 500), font_size=25, color=red)
                                # print('meta_d2h LOG------------------>:', log)
                    if left_mipi and right_mipi and is_IMU_connected:
                        if is_usb3:
                            self.device.reset_device_changed()
                        # for i in range(len(self.auto_checkbox_names)):
                        #     self.auto_checkbox_dict[self.auto_checkbox_names[i]].render_checkbox()
                        # print("device reste")
                        # print(self.device.is_device_changed())
                        break

            is_usb3 = self.device.is_usb3()
            left_status = self.device.is_left_connected()
            right_status = self.device.is_right_connected()

            if not is_usb3:
                self.auto_checkbox_dict["USB3"].uncheck()
            else:
                self.auto_checkbox_dict["USB3"].check()

            if not left_status:
                self.auto_checkbox_dict["Left camera connected"].uncheck()
            else:
                self.auto_checkbox_dict["Left camera connected"].check()

            if not left_mipi:
                self.auto_checkbox_dict["Left Stream"].uncheck()
            else:
                self.auto_checkbox_dict["Left Stream"].check()

            if not right_status:
                self.auto_checkbox_dict["Right camera connected"].uncheck()
            else:
                self.auto_checkbox_dict["Right camera connected"].check()

            if not right_mipi:
                # img = np.zeros((720, 1280, 3), np.uint8)
                # img[:] = (0, 0, 255)
                # cv2.putText(img, "right mipi",  (100, 300), cv2.FONT_HERSHEY_DUPLEX, 10.0, (0,0,0), 20)
                # cv2.putText(img, "Failed ", (140, 600), cv2.FONT_HERSHEY_SIMPLEX, 10.0, (0,0,0), 20)
                # cv2.imshow('right mipi camera failed', img)
                self.auto_checkbox_dict["Right Stream"].uncheck()
            else:
                self.auto_checkbox_dict["Right Stream"].check()

            if self.args['enable_IMU_test']:
                if is_IMU_connected:
                    self.auto_checkbox_dict["IMU connected"].check()
                else:
                    self.auto_checkbox_dict["IMU connected"].uncheck()

            for i in range(len(self.auto_checkbox_names)):
                self.auto_checkbox_dict[self.auto_checkbox_names[i]].render_checkbox()

        self.is_service_active = False
        return (True, self.device.get_mx_id())

    def calibration_servive_handler(self, req):
        self.is_service_active = True
        print("calibration Service Started")
        # pygame.draw.rect(self.screen, white, no_button)

        mx_serial_id = self.device.get_mx_id()
        calib_dest_path = os.path.join(arg['calib_path'], arg["board"] + '_' + mx_serial_id + '.calib')

        flags = [self.config['board_config']['stereo_center_crop']]
        cal_data = StereoCalibration()
        avg_epipolar_error, calib_data = cal_data.calibrate(
                            self.package_path + "/dataset",
                            self.args['square_size_cm'],
                            calib_dest_path,
                            flags,
                            req.name,
                            self.args['marker_size_cm'])

        text = "Avg Epipolar error : " + format(avg_epipolar_error, '.6f')
        pygame_render_text(self.screen, text, (400,160), green, 30)
        if avg_epipolar_error > 0.5:
            text = "Failed due to high calibration error"
            pygame_render_text(self.screen, text, (400,200), red, 30)
            return (False, text)
        # self.rundepthai()

        dev_config = {
            'board': {},
            '_board': {}
        }
        dev_config["board"]["clear-eeprom"] = False
        dev_config["board"]["store-to-eeprom"] = True
        dev_config["board"]["override-eeprom"] = False
        dev_config["board"]["swap-left-and-right-cameras"] = self.board_config['board_config']['swap_left_and_right_cameras']
        dev_config["board"]["left_fov_deg"] = self.board_config['board_config']['left_fov_deg']
        dev_config["board"]["rgb_fov_deg"] = self.board_config['board_config']['rgb_fov_deg']
        dev_config["board"]["left_to_right_distance_m"] = self.board_config['board_config']['left_to_right_distance_cm'] / 100
        dev_config["board"]["left_to_rgb_distance_m"] = self.board_config['board_config']['left_to_rgb_distance_cm'] / 100
        dev_config["board"]["name"] = self.board_config['board_config']['name']
        dev_config["board"]["stereo_center_crop"] = True
        dev_config["board"]["revision"] = self.board_config['board_config']['revision']
        dev_config["_board"]['calib_data'] = list(calib_data)
        dev_config["_board"]['mesh_right'] = [0.0]
        dev_config["_board"]['mesh_left'] =  [0.0]

        self.device.write_eeprom_data(dev_config)

        # calib_src_path = os.path.join(arg['depthai_path'], "resources/depthai.calib")
        # shutil.copy(calib_src_path, calib_dest_path)
        print("finished writing to EEPROM with Epipolar error of")

        print(avg_epipolar_error)
        print('Validating...')
        is_write_succesful = False
        run_thread = True
        while run_thread:
            _, data_packets = self.pipeline.get_available_nnet_and_data_packets(blocking=True)
            for packet in data_packets:
                if packet.stream_name == 'meta_d2h':
                    str_ = packet.getDataAsStr()
                    dict_ = json.loads(str_)
                    if 'logs' in dict_:
                        for log in dict_['logs']:
                            print(log)
                            if 'EEPROM' in log:
                                if 'write OK' in log:
                                    text = "EEPROM Write succesfull"
                                    pygame_render_text(self.screen, text, (400,200), green, 30)
                                    is_write_succesful = True
                                    run_thread = False
                                elif 'FAILED' in log:
                                    text = "EEPROM write Failed"
                                    pygame_render_text(self.screen, text, (400,200), red, 30)
                                    is_write_succesful = False
                                    run_thread = False
        self.is_service_active = False
        if is_write_succesful:
            return (True, "EEPROM written succesfully")
        else:
            return (False, "EEPROM write Failed!!")

    def rundepthai(self):
        test_cmd = "python3 " + self.args['depthai_path'] + "/depthai_demo.py -e -brd " + self.args['brd']
        print(test_cmd)
        # test_cmd = """python3 depthai.py -brd bw1098obc -e""" ## TODO(sachin): Parameterize the board name
        self.p = subprocess.Popen(test_cmd, shell=True, preexec_fn=os.setsid)
        time.sleep(4)
        os.killpg(os.getpgid(self.p.pid), signal.SIGKILL)

        time.sleep(1)

no_button =  pygame.Rect(490, 500, 80, 45)

if __name__ == "__main__":

    rospy.init_node('depthai_calibration', anonymous=True)
    arg = {}
    arg["swap_lr"] = rospy.get_param('~swap_lr')
    arg["field_of_view"] = rospy.get_param('~field_of_view')
    arg["baseline"] = rospy.get_param('~baseline')
    arg["package_path"] = rospy.get_param('~package_path')
    arg["square_size_cm"] = rospy.get_param('~square_size_cm')
    arg["marker_size_cm"] = rospy.get_param('~marker_size_cm')

    # arg["depthai_path"] = rospy.get_param('~depthai_path') ## Add  capture_checkerboard to launch file
    arg["board"] = rospy.get_param('~brd') ## Add  capture_checkerboard to launch file
    arg["capture_service_name"] = rospy.get_param('~capture_service_name') ## Add  capture_checkerboard to launch file
    arg["calibration_service_name"] = rospy.get_param('~calibration_service_name') ## Add  capture_checkerboard to launch file
    arg["depthai_path"] = rospy.get_param('~depthai_path') ## Path of depthai repo
    arg["enable_IMU_test"] = rospy.get_param('~enable_IMU_test')
    arg["calib_path"] = str(Path.home()) + rospy.get_param('~calib_path') ## local path to store calib files with using mx device id.
    print("Hone------------------------>")
    print(type(arg["enable_IMU_test"]))
    if not os.path.exists(arg['calib_path']):
        os.makedirs(arg['calib_path'])

    if arg['board']:
        board_path = Path(arg['board'])
        if not board_path.exists():
            board_path = Path(consts.resource_paths.boards_dir_path) / Path(arg['board'].upper()).with_suffix('.json')
            print(board_path)
            if not board_path.exists():
                raise ValueError('Board config not found: {}'.format(board_path))
        with open(board_path) as fp:
            board_config = json.load(fp)
    # assert os.path.exists(arg['depthai_path']), (arg['depthai_path'] +" Doesn't exist. \
    #     Please add the correct path using depthai_path:=[path] while executing launchfile")

    depthai_dev = depthai_calibration_node(arg)
    depthai_dev.publisher()
    rospy.spin()


#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
    codeauthor:: Valentin Secades <vsecades@qxdev.com>

    check_ip
        abuseipdb.check_ip(ip="[IP]",days="[DAYS]")

    check_cidr
        abuseipdb.check_cidr(cidr="[CIDR]",days="[DAYS]")

    report_ip
        abuseipdb.report_ip(categories="[CATEGORIES]", comment="[OPTIONAL COMMENT]", ip="[IP]")

    Field 	Required 	Default 	Example 	           Description
    [IP] 	Y 	          NA 	   8.8.8.8 	               IPv4 Address
    [DAYS] 	N 	          30 	      30 	               Check for IP Reports in the last 30 days.
    [CIDR] 	Y 	          NA  	207.126.144.0/20 	       IPv4 Address Block in CIDR notation
    [CATEGORIES] 	Y 	  NA 	  10,12,15 	               Comma delineated list of category IDs
    [OPTIONAL COMMENT] 	N 	NA 	This is a comment. 	       Describe the type of malicious activity
    [API KEY] 	Y 	      NA 	Tzmp1...quWvaiO 	       Your API key.

"""
import requests

class Parameters():

    configuration = {}
    url_templates = {
        "check_ip" : "https://www.abuseipdb.com/check/[IP]/json?key=[API_KEY]&days=[DAYS]",
        "check_cidr" : "https://www.abuseipdb.com/check-block/json?key=[API_KEY]&network=[CIDR]&days=[DAYS]",
        "report_ip" : "https://www.abuseipdb.com/report/json?key=[API_KEY]&category=[CATEGORIES]&comment=[COMMENT]&ip=[IP]",
    }
    defaults = {
        "days" : "30"
    }

    @staticmethod
    def get_config():
        return Parameters.configuration

    @staticmethod
    def set_config(config):
        Parameters.configuration = Parameters.dict_merge([Parameters.configuration, config])

    @staticmethod
    def merge_recursive(source, destination):
        """

        :rtype: dict
        """
        for key, value in source.items():
            if isinstance(value, dict):
                # get node or create one
                node = destination.setdefault(key, {})
                Parameters.merge_recursive(value, node)
            else:
                destination[key] = value

        return destination

    # taken from https://stackoverflow.com/questions/20656135/python-deep-merge-dictionary-data using recursion
    @staticmethod
    def dict_merge(list_dicts=[]):
        # print("List received: ")
        # pp = pprint.PrettyPrinter(indent=4)
        # pp.pprint(list_dicts)
        # Merge multiple dicts
        target = {}
        while len(list_dicts) > 0:
            temp_dict = list_dicts.pop()
            # pp.pprint(temp_dict)
            target = Parameters.merge_recursive(target, temp_dict)
            # pp.pprint(target)
        return target

def configure_api_key(api_key):
    #Check that api_key is not None OR that it has been set previously
    if api_key is not None:
        Parameters.set_config({"API_KEY": api_key})
    else:
        print("Api key cannot be blank")

def check_ip(ip=None,days=Parameters.defaults["days"]):
    #used to check an IP for reports
    if ip is not None:
        request_url = Parameters.url_templates["check_ip"]
        request_url = request_url.replace("[IP]",ip)
        request_url = request_url.replace("[API_KEY]",Parameters.get_config()["API_KEY"])
        request_url = request_url.replace("[DAYS]",days)
        print(request_url)
        response = requests.get(request_url)
        # return raw for now, we will add decorators later on
        return response.text
    else:
        print("ip is not defined")
        return None

def check_cidr(cidr=None,days=Parameters.defaults["days"]):
    # used to check an IP for reports
    if cidr is not None:
        request_url = Parameters.url_templates["check_cidr"]
        request_url = request_url.replace("[CIDR]", cidr)
        request_url = request_url.replace("[API_KEY]", Parameters.get_config()["API_KEY"])
        request_url = request_url.replace("[DAYS]", days)
        print(request_url)
        response = requests.get(request_url)
        # return raw for now, we will add decorators later on
        return response.text
    else:
        print("CIDR is not defined")
        return None

def report_ip(categories=None, comment="", ip=None):
    # used to check an IP for reports
    if ip is not None and categories is not None:
        request_url = Parameters.url_templates["report_ip"]
        request_url = request_url.replace("[IP]", ip)
        request_url = request_url.replace("[API_KEY]", Parameters.get_config()["API_KEY"])
        request_url = request_url.replace("[COMMENT]", comment)
        request_url = request_url.replace("[CATEGORIES]", categories)
        print(request_url)
        response = requests.get(request_url)
        # return raw for now, we will add decorators later on
        return response.text
    else:
        print("ip is not defined")
        return None


## Portions of Code from, copyright 2018 Jochen Gast

from __future__ import absolute_import, division, print_function

import argparse
import colorama
import inspect
import os
import sys
import torch
import logging

import datasets
import losses
import models
import augmentations

from core import tools, logger, optim

def _get_type_from_arg(arg):
    if isinstance(arg, bool):
        return tools.str2bool
    else:
        return type(arg)

def _add_arguments_for_module(parser,
                              module,
                              name,
                              default_class,
                              add_class_argument=True,   # whether to add class choice as argument
                              include_classes="*",
                              exclude_classes=[],
                              exclude_params=["self","args"],
                              param_defaults={},          # allows to overwrite any default param
                              forced_default_types={},    # allows to set types for known arguments
                              unknown_default_types={}):  # allows to set types for unknown arguments

    # -------------------------------------------------------------------------
    # Determine possible choices from class names in module, possibly apply include/exclude filters
    # -------------------------------------------------------------------------
    module_dict = tools.module_classes_to_dict(
        module, include_classes=include_classes, exclude_classes=exclude_classes)

    # -------------------------------------------------------------------------
    # Parse known arguments to determine choice for argument name
    # -------------------------------------------------------------------------
    if add_class_argument:
        parser.add_argument(
            "--%s" % name, type=str, default=default_class, choices=module_dict.keys())
        known_args = parser.parse_known_args(sys.argv[1:])[0]
    else:
        # build a temporary parser, and do not add the class as argument
        tmp_parser = argparse.ArgumentParser()
        tmp_parser.add_argument(
            "--%s" % name, type=str, default=default_class, choices=module_dict.keys())
        known_args = tmp_parser.parse_known_args(sys.argv[1:])[0]

    class_name = vars(known_args)[name]

    # -------------------------------------------------------------------------
    # If class is None, there is no point in trying to parse further arguments
    # -------------------------------------------------------------------------
    if class_name is None:
        return

    # -------------------------------------------------------------------------
    # Get constructor of that argument choice
    # -------------------------------------------------------------------------
    class_constructor = module_dict[class_name]

    # -------------------------------------------------------------------------
    # Determine constructor argument names and defaults
    # -------------------------------------------------------------------------
    try:
        argspec = inspect.getargspec(class_constructor.__init__)
        argspec_defaults = argspec.defaults if argspec.defaults is not None else []
        full_args = argspec.args
        default_args_dict = dict(zip(argspec.args[-len(argspec_defaults):], argspec_defaults))
    except TypeError:
        print(argspec)
        print(argspec.defaults)
        raise ValueError("unknown_default_types should be adjusted for module: '%s.py'" % name)

    # -------------------------------------------------------------------------
    # Add sub_arguments
    # -------------------------------------------------------------------------
    for argname in full_args:

        # ---------------------------------------------------------------------
        # Skip
        # ---------------------------------------------------------------------
        if argname in exclude_params:
            continue

        # ---------------------------------------------------------------------
        # Sub argument name
        # ---------------------------------------------------------------------
        sub_arg_name = "%s_%s" % (name, argname)

        # ---------------------------------------------------------------------
        # If a default argument is given, take that one
        # ---------------------------------------------------------------------
        if argname in param_defaults.keys():
            parser.add_argument(
                "--%s" % sub_arg_name,
                type=_get_type_from_arg(param_defaults[argname]),
                default=param_defaults[argname])

        # ---------------------------------------------------------------------
        # If a default parameter can be inferred from the module, pick that one
        # ---------------------------------------------------------------------
        elif argname in default_args_dict.keys():

            # -----------------------------------------------------------------
            # Check for forced default types
            # -----------------------------------------------------------------
            if argname in forced_default_types.keys():
                argtype = forced_default_types[argname]
            else:
                argtype = _get_type_from_arg(default_args_dict[argname])
            parser.add_argument(
                "--%s" % sub_arg_name, type=argtype, default=default_args_dict[argname])

        # ---------------------------------------------------------------------
        # Take from the unkowns list
        # ---------------------------------------------------------------------
        elif argname in unknown_default_types.keys():
            parser.add_argument("--%s" % sub_arg_name, type=unknown_default_types[argname])

        else:
            raise ValueError(
                "Do not know how to handle argument '%s' for class '%s'" % (argname, name))

def _add_special_arguments(parser):
    # -------------------------------------------------------------------------
    # Known arguments so far
    # -------------------------------------------------------------------------
    known_args = vars(parser.parse_known_args(sys.argv[1:])[0])

    # -------------------------------------------------------------------------
    # Add special arguments for training
    # -------------------------------------------------------------------------
    training_loss = known_args["training_loss"]
    if training_loss is not None:
        parser.add_argument("--training_key", type=str, default="total_loss")

    # -------------------------------------------------------------------------
    # Add special arguments for validation
    # -------------------------------------------------------------------------
    validation_loss = known_args["validation_loss"]
    if validation_loss is not None:
        parser.add_argument("--validation_key", type=str, default="total_loss")
        parser.add_argument("--validation_key_minimize", type=tools.str2bool, default=True)

    # -------------------------------------------------------------------------
    # Add special arguments for checkpoints
    # -------------------------------------------------------------------------
    checkpoint = known_args["checkpoint"]
    if checkpoint is not None:
        parser.add_argument(
            "--checkpoint_mode", type=str, default="resume_from_latest",
            choices=["resume_from_latest", "resume_from_best"])

        parser.add_argument(
            "--checkpoint_include_params", type=tools.str2list, default="[*]")
        parser.add_argument(
            "--checkpoint_exclude_params", type=tools.str2list, default="[]")

    # -------------------------------------------------------------------------
    # Add special arguments for optimizer groups
    # -------------------------------------------------------------------------
    parser.add_argument("--optimizer_group", action="append", type=tools.str2dict, default=None)

def _parse_arguments():

    # -------------------------------------------------------------------------
    # Argument parser and shortcut function to add arguments
    # -------------------------------------------------------------------------
    parser = argparse.ArgumentParser()
    add = parser.add_argument

    # -------------------------------------------------------------------------
    # Standard arguments
    # -------------------------------------------------------------------------
    add("--batch_size", type=int, default=1)
    add("--batch_size_val", type=int, default=1)
    add("--checkpoint", type=tools.str2str_or_none, default=None)
    add("--cuda", type=tools.str2bool, default=True)
    add("--evaluation", type=tools.str2bool, default=False)
    add("--num_workers", type=int, default=4)
    add("--save", "-s", default="temp_exp/", type=str)
    add("--seed", type=int, default=1)
    add("--start_epoch", type=int, default=1)
    add("--total_epochs", type=int, default=1)
    add("--save_flow", type=tools.str2bool, default=False)
    add("--save_disp", type=tools.str2bool, default=False)
    add("--save_disp2", type=tools.str2bool, default=False)
    add("--finetuning", type=tools.str2bool, default=False)
    ### for c3d
    add("--c3d_config_file", type=str, default="scripts/c3d_config.txt")
    add("--inbalance_to_closer", type=float, default=1, help="used in L1 depth loss, which favors closer GT depth when GT depth pcl has overlapping area caused by viewing angle difference. ")
    add("--disable_c3d", action="store_true", help="if specified, not applying any c3d loss")
    add("--save_per_epoch", type=int, default=0)
    add("--dep_warp_loss", action="store_true", help="if specified, any depth l1 loss on warped depth")
    add("--dep_weight", type=float, default=1e-3, help="weight for depth l1 loss ")
    add("--c3d_weight", type=float, default=1e-4, help="weight for c3d loss ")
    add("--c3d_knn_mode", action="store_true", help="if specified, use c3d loss calculated based on knn")
    add("--rigid", action="store_true", help="if set, predict camera motion and use it to compose the rigid flow")
    add("--rigid_pred", type=str, default="full", choices=["full", "res"], help="decide the meaning of the flow prediction. ")
    add("--rigid_warp", type=str, default="full", choices=["full", "res", "rigid"], help="how to warp next level feature maps before constructing cost volume. ")
    add("--rigid_pass", type=str, default="warp", choices=["warp", "pred"], help="what to feed to next level flow decoder. ")
    add("--save_pic_tboard", action="store_true", help="if specified, show visualization in tensorboard. ")
    add("--disable_residual_pred", action="store_true", help="if specified, predict full flow instead of add-on to last level predictions. ")
    add("--reg_depth", action="store_true", help="if specified, predict depth in meters, instead of normalized disparity ")
    add("--hourglass_decoder", action="store_true", help="if specified, predict using an hourglass structure (U-Net based on ResNet18) with resuable weight and consistent resolution. ")
    add("--highres_loss", action="store_true", help="if specified, using high resolution predictions to calculate loss. Only valid if hourglass_decoder is chosen.  ")
    add("--rigid_flow_loss", action="store_true", help="if specified, calculate loss for rigid flow separately. Otherwise just penalize difference between rigid flow and full flow at masked region.  ")
    add("--strict_flow_acc", action="store_true", help="if specified, accumulate the residual flow considering the shift of flow origin.  ")
    add("--strict_flow_acc_ramp", type=int, default=0, help="if greater than zero, gradually shift from original residual flow to warped residual flow in specified epochs. ")
    add("--c3d_pose_weight", type=float, default=0, help="if greater than zero, calculate pose loss using c3d loss between gt lidar scans with transformation. ")
    add("--shared_posehead", action="store_true", help="if specified, use shared pose predictor. Only valid if not using hourglass_decoder. ")
    add("--pose_not_separable", action="store_true", help="if specified, use FCN instead of two separate layers in spatial and feature dimensions.  ")
    add("--use_posenet", action="store_true", help="if specified, use a dedicated pose net for pose regression.  ")
    add("--strict_flow_acc_uvd", action="store_true", help="if specified, use uvd prediction as sceneflow representation and concat using strict recursive inference.  ")
    add("--pred_xyz_add_uvd", action="store_true", help="if specified, use uvd to aggregate while use xyz in prediction.  ")

    # -------------------------------------------------------------------------
    # Arguments inferred from losses
    # -------------------------------------------------------------------------
    _add_arguments_for_module(
        parser,
        losses,
        name="training_loss",
        default_class=None,
        exclude_classes=["_*", "Variable"],
        exclude_params=["self","args"])

    _add_arguments_for_module(
        parser,
        losses,
        name="validation_loss",
        default_class=None,
        exclude_classes=["_*", "Variable"],
        exclude_params=["self","args"])

    # -------------------------------------------------------------------------
    # Arguments inferred from models
    # -------------------------------------------------------------------------
    _add_arguments_for_module(
        parser,
        models,
        name="model",
        default_class="FlowNet1S",
        exclude_classes=["_*", "Variable"],
        exclude_params=["self","args"])

    # -------------------------------------------------------------------------
    # Arguments inferred from augmentations for training
    # -------------------------------------------------------------------------
    _add_arguments_for_module(
        parser,
        augmentations,
        name="training_augmentation",
        default_class=None,
        exclude_classes=["_*"],
        exclude_params=["self","args"],
        forced_default_types={"crop": tools.str2intlist})

    # -------------------------------------------------------------------------
    # Arguments inferred from augmentations for validation
    # -------------------------------------------------------------------------
    _add_arguments_for_module(
        parser,
        augmentations,
        name="validation_augmentation",
        default_class=None,
        exclude_classes=["_*"],
        exclude_params=["self","args"])

    # -------------------------------------------------------------------------
    # Arguments inferred from datasets for training
    # -------------------------------------------------------------------------
    _add_arguments_for_module(
        parser,
        datasets,
        name="training_dataset",
        default_class=None,
        exclude_params=["self", "args", "is_cropped"],
        exclude_classes=["_*"],
        unknown_default_types={"root": str})

    # -------------------------------------------------------------------------
    # Arguments inferred from datasets for validation
    # -------------------------------------------------------------------------
    _add_arguments_for_module(
        parser,
        datasets,
        name="validation_dataset",
        default_class=None,
        exclude_params=["self", "args", "is_cropped"],
        exclude_classes=["_*"],
        unknown_default_types={"root": str})

    # -------------------------------------------------------------------------
    # Arguments inferred from PyTorch optimizers
    # -------------------------------------------------------------------------
    _add_arguments_for_module(
        parser,
        optim,
        name="optimizer",
        default_class="Adam",
        exclude_classes=["_*","Optimizer", "constructor"],
        exclude_params=["self", "args", "params"],
        forced_default_types={"lr": float,
                              "momentum": float,
                              "dampening": float,
                              "weight_decay": float,
                              "nesterov": tools.str2bool})

    # -------------------------------------------------------------------------
    # Arguments inferred from PyTorch lr schedulers
    # -------------------------------------------------------------------------
    _add_arguments_for_module(
        parser,
        torch.optim.lr_scheduler,
        name="lr_scheduler",
        default_class=None,
        exclude_classes=["_*","Optimizer"],
        exclude_params=["self", "args", "optimizer"],
        unknown_default_types={"T_max": int,
                               "lr_lambda": str,
                               "step_size": int,
                               "milestones": tools.str2intlist,
                               "gamma": float})

    # -------------------------------------------------------------------------
    # Special arguments
    # -------------------------------------------------------------------------
    _add_special_arguments(parser)

    # -------------------------------------------------------------------------
    # Parse arguments
    # -------------------------------------------------------------------------
    args = parser.parse_args()

    # -------------------------------------------------------------------------
    # Parse default arguments from a dummy commandline not specifying any args
    # -------------------------------------------------------------------------
    defaults = vars(parser.parse_known_args(['--dummy'])[0])

    # -------------------------------------------------------------------------
    # Consistency checks
    # -------------------------------------------------------------------------
    args.cuda = args.cuda and torch.cuda.is_available()

    return args, defaults

def postprocess_args(args):

    # ----------------------------------------------------------------------------
    # Get appropriate class constructors from modules
    # ----------------------------------------------------------------------------
    args.model_class = tools.module_classes_to_dict(models)[args.model]

    if args.optimizer is not None:
        optimizer_classes = tools.module_classes_to_dict(optim)
        args.optimizer_class = optimizer_classes[args.optimizer]

    if args.training_loss is not None:
        loss_classes = tools.module_classes_to_dict(losses)
        args.training_loss_class = loss_classes[args.training_loss]

    if args.validation_loss is not None:
        loss_classes = tools.module_classes_to_dict(losses)
        args.validation_loss_class = loss_classes[args.validation_loss]

    if args.lr_scheduler is not None:
        scheduler_classes = tools.module_classes_to_dict(torch.optim.lr_scheduler)
        args.lr_scheduler_class = scheduler_classes[args.lr_scheduler]

    if args.training_dataset is not None:
        dataset_classes = tools.module_classes_to_dict(datasets)
        args.training_dataset_class = dataset_classes[args.training_dataset]

    if args.validation_dataset is not None:
        dataset_classes = tools.module_classes_to_dict(datasets)
        args.validation_dataset_class = dataset_classes[args.validation_dataset]

    if args.training_augmentation is not None:
        augmentation_classes = tools.module_classes_to_dict(augmentations)
        args.training_augmentation_class = augmentation_classes[args.training_augmentation]

    if args.validation_augmentation is not None:
        augmentation_classes = tools.module_classes_to_dict(augmentations)
        args.validation_augmentation_class = augmentation_classes[args.validation_augmentation]

    return args

def setup_logging_and_parse_arguments(blocktitle):
    # ----------------------------------------------------------------------------
    # Get parse commandline and default arguments
    # ----------------------------------------------------------------------------
    args, defaults = _parse_arguments()

    # ----------------------------------------------------------------------------
    # Setup logbook before everything else
    # ----------------------------------------------------------------------------
    logger.configure_logging(os.path.join(args.save, 'logbook.txt'))

    # ----------------------------------------------------------------------------
    # Write arguments to file, as txt
    # ----------------------------------------------------------------------------
    tools.write_dictionary_to_file(
        sorted(vars(args).items()),
        filename=os.path.join(args.save, 'args.txt'))

    # ----------------------------------------------------------------------------
    # Log arguments
    # ----------------------------------------------------------------------------
    with logger.LoggingBlock(blocktitle, emph=True):
        for argument, value in sorted(vars(args).items()):
            reset = colorama.Style.RESET_ALL
            color = reset if value == defaults[argument] else colorama.Fore.CYAN
            logging.info('{}{}: {}{}'.format(color, argument, value, reset))

    # ----------------------------------------------------------------------------
    # Postprocess
    # ----------------------------------------------------------------------------
    args = postprocess_args(args)

    return args


import datagram
import protocol

class Util(object):

    @staticmethod
    def send(socket, data):
        message = datagram.Datagram.encode(
                    protocol.FileProtocol.encode(data)
                  )
        print("SENT " + message)
        Util._doSend(socket, message)

    @staticmethod
    def _doSend(socket, message):
        sent = 0
        while sent < len(message):
            sent += socket.send(message[sent:])

    @staticmethod
    def recive(socket):

        buffer = Util._doRecive(socket, datagram.Datagram.LENGTH)

        buffer += Util._doRecive(socket, datagram.Datagram.getLength(buffer))

        print("RECIVED " + buffer)

        return protocol.FileProtocol.decode(
            datagram.Datagram.decode(buffer)
        )

    @staticmethod
    def _doRecive(socket, length):
        buffer = ""
        while len(buffer) < length:
            buffer += socket.recv(length - len(buffer))
        return buffer


import logging
import os
import time

import boto3
from botocore.exceptions import ClientError

log = logging.getLogger('assetModelConverter')
log.setLevel(logging.DEBUG)

class ExampleDriverTemplate:
    def __init__(self):
        self.boto3_session = boto3.Session()
        self.dynamoClient = self.boto3_session.resource('dynamodb')
        self.assetModelTable = self.dynamoClient.Table(os.environ['DynamoDB_Model_Table'])
        self.assetTable = self.dynamoClient.Table(os.environ['DynamoDB_Asset_Table'])

    def createDynamoRecords(self, table, data, primaryKey):
        for record in data:
            try:
                table.put_item(Item=record, ConditionExpression=f'attribute_not_exists({primaryKey})')
                time.sleep(0.1)

            except ClientError as cErr:
                if cErr.response['Error']['Code'] == 'ConditionalCheckFailedException':
                    log.info('Ignoring existing record {}'.format(record[primaryKey]))
                else:
                    raise

    def processEvent(self, event):
        # event['birthData'] contains a list[] of ingested model files, loaded as JSON into python dictionary form.
        # Your edge software may produce one or more of these model files, and they should all be presented to you here.
        modelFileData = event['birthData']

        # From here, process the modelFileData, and then populate the self.assetModelTable and self.assetTable
        # dynamoDB tables. Please see the 'Getting Started' guide for documentation on this format, or reference the
        # example Ignition and/or KepServer drivers.

def handler(event, context):
    ExampleDriverTemplate().processEvent(event)


#!/usr/bin/env python3
"""
File:          siv_test.py
Author:        Binit Shah
Last Modified: Binit on 2/22
"""

import math
from localization import particle_filter, utilities

if __name__ == "__main__":
    viz = utilities.Visualization()
    grid = particle_filter.Grid()
    particles = grid.initialize_particles(10)
    robot = particle_filter.Particle(utilities.inch_to_meters(9.5), utilities.inch_to_meters(22.5), math.radians(0.0))
    pfilter = particle_filter.ParticleFilter(particles, robot, grid)
    # print(f"pre ({robot.x_m, robot.y_m, robot.theta_rad})")
    # print("pre is_free: ", grid.is_free(robot.x_m, robot.y_m, robot.theta_rad))
    # pfilter.motion_update((3, 3), 4.5)
    # pfilter.motion_update((1, 2), 2)
    # print(f"post ({robot.x_m, robot.y_m, robot.theta_rad})")
    # print("post is_free: ", grid.is_free(robot.x_m, robot.y_m, robot.theta_rad))

    while True:
        viz.draw_background(grid)
        viz.draw_particles(particles)
        viz.draw_robot(robot)
        print(f"robot_pose: ({robot.x_m, robot.y_m, robot.theta_rad})")
        viz.update()
        x = int(input("x = "))
        y = int(input("y = "))
        pfilter.motion_update((x, y), 1)


import posixpath
import re

from egginst.vendor import zipfile2

from ..bundled.traitlets import (
    HasTraits, Instance, List, Long, Unicode
)
from ..errors import (
    InvalidRequirementString, InvalidEggName, InvalidMetadata,
    InvalidMetadataField, MissingMetadata, UnsupportedMetadata
)
from ..platforms import EPDPlatform, PythonImplementation
from ..platforms.legacy import LegacyEPDPlatform
from ..utils import compute_sha256, parse_assignments
from ..utils.py3compat import StringIO, string_types
from ..utils.traitlets import NoneOrInstance, NoneOrUnicode
from ..versions import EnpkgVersion, MetadataVersion
from ._blacklist import (
    EGG_PLATFORM_BLACK_LIST, EGG_PYTHON_TAG_BLACK_LIST,
    may_be_in_platform_blacklist, may_be_in_python_tag_blacklist,
    may_be_in_pkg_info_blacklist
)
from ._package_info import PackageInfo, _keep_position, _read_pkg_info

_EGG_NAME_RE = re.compile("""
    (?P<name>[\.\w]+)
    -
    (?P<version>[^-]+)
    -
    (?P<build>\d+)
    \.egg$""", re.VERBOSE)

_PYVER_RE = re.compile("(?P<major>\d+)\.(?P<minor>\d+)")

EGG_INFO_PREFIX = "EGG-INFO"

# Those may need to be public, depending on how well we can hide their
# locations or not.
_INFO_JSON_LOCATION = posixpath.join(EGG_INFO_PREFIX, "info.json")
_SPEC_DEPEND_LOCATION = posixpath.join(EGG_INFO_PREFIX, "spec", "depend")
_SPEC_LIB_DEPEND_LOCATION = posixpath.join(EGG_INFO_PREFIX, "spec",
                                           "lib-depend")
_SPEC_SUMMARY_LOCATION = posixpath.join(EGG_INFO_PREFIX, "spec", "summary")
_USR_PREFIX_LOCATION = posixpath.join(EGG_INFO_PREFIX, "usr")

_TAG_METADATA_VERSION = "metadata_version"
_TAG_NAME = "name"
_TAG_VERSION = "version"
_TAG_BUILD = "build"
_TAG_ARCH = "arch"
_TAG_OSDIST = "osdist"
_TAG_PLATFORM = "platform"
_TAG_PYTHON = "python"
_TAG_PYTHON_PEP425_TAG = "python_tag"
_TAG_ABI_PEP425_TAG = "abi_tag"
_TAG_PLATFORM_PEP425_TAG = "platform_tag"
_TAG_PACKAGES = "packages"

M = MetadataVersion.from_string

_METADATA_VERSION_TO_KEYS = {
    M("1.1"): (
        _TAG_METADATA_VERSION, _TAG_NAME, _TAG_VERSION, _TAG_BUILD, _TAG_ARCH,
        _TAG_PLATFORM, _TAG_OSDIST, _TAG_PYTHON, _TAG_PACKAGES
    ),
}
_METADATA_VERSION_TO_KEYS[M("1.2")] = \
    _METADATA_VERSION_TO_KEYS[M("1.1")] + (_TAG_PYTHON_PEP425_TAG, )

_METADATA_VERSION_TO_KEYS[M("1.3")] = (
    _METADATA_VERSION_TO_KEYS[M("1.2")] +
    (_TAG_ABI_PEP425_TAG, _TAG_PLATFORM_PEP425_TAG)
)

_UNSUPPORTED = "unsupported"

_PYVER_RE = re.compile("(?P<major>\d).(?P<minor>\d)")

def _are_compatible(left, right):
    """Return True if both arguments are compatible metadata versions.

    Parameters
    ----------
    left: MetadataVersion
    right: MetadataVersion
    """
    return left.major == right.major

def _highest_compatible(metadata_version):
    """ Returns the highest metadata version supporting that is compatible with
    the given version.
    """
    compatible_versions = [
        m for m in _METADATA_VERSION_TO_KEYS
        if _are_compatible(m, metadata_version)
    ]

    if len(compatible_versions) > 0:
        return max(compatible_versions)
    else:
        raise UnsupportedMetadata(metadata_version)

def split_egg_name(s):
    m = _EGG_NAME_RE.match(s)
    if m is None:
        raise InvalidEggName(s)
    else:
        name, version, build = m.groups()
        return name, version, int(build)

def parse_rawspec(spec_string):
    spec = parse_assignments(StringIO(spec_string.replace('\r', '')))

    metadata_version_string = spec.get(_TAG_METADATA_VERSION)
    if metadata_version_string is not None:
        metadata_version = MetadataVersion.from_string(metadata_version_string)
    else:
        metadata_version = None

    if metadata_version is None:
        raise InvalidMetadataField('metadata_version', metadata_version_string)
    elif metadata_version not in _METADATA_VERSION_TO_KEYS:
        metadata_version = _highest_compatible(metadata_version)

    res = {}

    keys = _METADATA_VERSION_TO_KEYS.get(metadata_version)
    for key in keys:
        try:
            res[key] = spec[key]
        except KeyError:
            raise InvalidMetadataField(key, InvalidMetadataField.undefined)
    return res

def egg_name(name, version, build):
    """
    Return the egg filename (including the .egg extension) for the given
    arguments
    """
    return "{0}-{1}-{2}.egg".format(name, version, build)

def is_egg_name_valid(s):
    """
    Return True if the given string is a valid egg name (not including the
    .egg, e.g. 'Qt-4.8.5-2')
    """
    return _EGG_NAME_RE.match(s) is not None

_INVALID_REQUIREMENTS = {
    "numpy-1.8.0": "numpy 1.8.0",
}

def _translate_invalid_requirement(s):
    return _INVALID_REQUIREMENTS.get(s, s)

class Requirement(HasTraits):
    """
    Model for entries in the package metadata inside EGG-INFO/spec/depend
    """
    name = Unicode()
    version_string = Unicode()
    build_number = Long(-1)

    def __init__(self, name="", version_string="", build_number=-1):
        self.name = name
        self.version_string = version_string
        self.build_number = build_number
        super(Requirement, self).__init__(self, name, version_string,
                                          build_number)

    @property
    def strictness(self):
        if len(self.version_string) == 0:
            return 1
        elif self.build_number < 0:
            return 2
        else:
            return 3

    @classmethod
    def from_string(cls, s, strictness=2):
        """
        Create a Requirement from string following a name-version-build
        format.

        Parameters
        ----------
        s: str
            Egg name, e.g. 'Qt-4.8.5-2'.
        strictness: int
            Control strictness of string representation
        """
        name, version, build = split_egg_name("{0}.egg".format(s))
        if strictness >= 3:
            build_number = build
        else:
            build_number = -1

        if strictness >= 2:
            version_string = version
        else:
            version_string = ""

        return cls(name=name, version_string=version_string,
                   build_number=build_number)

    @classmethod
    def from_spec_string(cls, s):
        """
        Create a Requirement from a spec string (as used in
        EGG-INFO/spec/depend).
        """
        s = _translate_invalid_requirement(s)
        parts = s.split()
        if len(parts) == 1:
            name = parts[0]
            if "-" in name:
                raise InvalidRequirementString(name)
            return cls(name=name)
        elif len(parts) == 2:
            name, version = parts
            parts = version.split("-")
            if len(parts) == 2:
                upstream, build_number = parts
                build_number = int(build_number)
            else:
                upstream, build_number = version, -1
            return cls(name=name, version_string=upstream,
                       build_number=build_number)
        else:
            raise InvalidRequirementString(name)

    def __str__(self):
        if len(self.version_string) > 0:
            if self.build_number > 0:
                return "{0} {1}-{2}".format(self.name,
                                            self.version_string,
                                            self.build_number)
            else:
                return "{0} {1}".format(self.name, self.version_string)
        else:
            return self.name

    @property
    def _key(self):
        return (self.name, self.version_string, self.build_number)

    def __eq__(self, other):
        if not isinstance(other, type(self)):
            return False
        return self._key == other._key

    def __ne__(self, other):
        return not (self == other)

    def __hash__(self):
        return hash(self._key)

_METADATA_TEMPLATES = {
    M("1.1"): """\
metadata_version = '1.1'
name = {name!r}
version = {version!r}
build = {build}

arch = {arch!r}
platform = {platform!r}
osdist = {osdist!r}
python = {python!r}
packages = {packages}
""",
    M("1.2"): """\
metadata_version = '1.2'
name = {name!r}
version = {version!r}
build = {build}

arch = {arch!r}
platform = {platform!r}
osdist = {osdist!r}
python = {python!r}
python_tag = {python_tag!r}
packages = {packages}
""",
    M("1.3"): """\
metadata_version = '1.3'
name = {name!r}
version = {version!r}
build = {build}

arch = {arch!r}
platform = {platform!r}
osdist = {osdist!r}
python = {python!r}

python_tag = {python_tag!r}
abi_tag = {abi_tag!r}
platform_tag = {platform_tag!r}

packages = {packages}
"""
}

def _guess_python_tag(pyver):
    """ Guess python_tag from the given python string ("MAJOR.MINOR", e.g. "2.7").

    None may be returned (for egg that don't depend on python)
    """
    if pyver in (None, ""):
        return None
    else:
        m = _PYVER_RE.search(pyver)
        if m is None:
            msg = "python_tag cannot be guessed for python = {0}"
            raise InvalidMetadata(msg.format(pyver))
        else:
            major = m.groupdict()["major"]
            minor = m.groupdict()["minor"]

            return "cp" + major + minor

_METADATA_DEFAULT_VERSION_STRING = "1.3"
_METADATA_DEFAULT_VERSION = M(_METADATA_DEFAULT_VERSION_STRING)

def _epd_platform_from_raw_spec(raw_spec):
    """ Create an EPDPlatform instance from the metadata info returned by
    parse_rawspec.

    if no platform is defined ('platform' and 'osdist' set to None), then
    None is returned.
    """
    arch_string = raw_spec[_TAG_ARCH]
    platform_string = raw_spec[_TAG_PLATFORM]
    osdist_string = raw_spec[_TAG_OSDIST]
    if platform_string is None and osdist_string is None:
        return None
    else:
        return EPDPlatform._from_spec_depend_data(
            platform_string, osdist_string, arch_string
        )

class LegacySpecDepend(HasTraits):
    """
    This models the EGG-INFO/spec/depend content.
    """
    # Name is taken from egg path, so may be upper case
    name = Unicode()
    """
    Egg name
    """
    version = Unicode()
    """
    Upstream version (as a string).
    """
    build = Long()
    """
    Build number
    """

    python = NoneOrUnicode()
    """
    Python version
    """

    python_tag = NoneOrUnicode()
    """
    Python tag (as defined in PEP 425).
    """

    abi_tag = NoneOrUnicode()
    """
    ABI tag (as defined in PEP 425), except that 'none' is None.
    """

    platform_tag = NoneOrUnicode()
    """
    Platform tag (as defined in PEP 425), except that 'any' is None.
    """

    packages = List(Instance(Requirement))
    """
    List of dependencies for this egg
    """

    _epd_legacy_platform = NoneOrInstance(LegacyEPDPlatform)

    _metadata_version = Instance(MetadataVersion)

    @classmethod
    def _from_data(cls, data, epd_platform):
        args = data.copy()
        args[_TAG_METADATA_VERSION] = M(
            args.get(_TAG_METADATA_VERSION, _METADATA_DEFAULT_VERSION_STRING)
        )

        if epd_platform is None:
            _epd_legacy_platform = None
        else:
            _epd_legacy_platform = LegacyEPDPlatform(epd_platform)
        args["_epd_legacy_platform"] = _epd_legacy_platform

        args[_TAG_PACKAGES] = [
            Requirement.from_spec_string(s)
            for s in args.get(_TAG_PACKAGES, [])
        ]

        return cls(**args)

    @classmethod
    def from_egg(cls, path_or_file):
        sha256 = None
        if isinstance(path_or_file, string_types):
            if (
                may_be_in_platform_blacklist(path_or_file)
                or may_be_in_python_tag_blacklist(path_or_file)
            ):
                sha256 = compute_sha256(path_or_file)
        else:
            with _keep_position(path_or_file.fp):
                sha256 = compute_sha256(path_or_file.fp)
        return cls._from_egg(path_or_file, sha256)

    @classmethod
    def _from_egg(cls, path_or_file, sha256):
        def _create_spec_depend(zp):
            epd_platform_string = EGG_PLATFORM_BLACK_LIST.get(sha256)
            if epd_platform_string is None:
                epd_platform = None
            else:
                epd_platform = EPDPlatform.from_epd_string(epd_platform_string)

            try:
                spec_depend_string = zp.read(_SPEC_DEPEND_LOCATION).decode()
            except KeyError:
                msg = ("File {0!r} is not an Enthought egg (is missing {1})"
                       .format(path_or_file, _SPEC_DEPEND_LOCATION))
                raise MissingMetadata(msg)
            else:
                data, epd_platform = _normalized_info_from_string(
                    spec_depend_string, epd_platform, sha256
                )
                return cls._from_data(data, epd_platform)

        if isinstance(path_or_file, string_types):
            with zipfile2.ZipFile(path_or_file) as zp:
                return _create_spec_depend(zp)
        else:
            return _create_spec_depend(path_or_file)

    @classmethod
    def from_string(cls, spec_depend_string):
        data, epd_platform = _normalized_info_from_string(spec_depend_string)
        return cls._from_data(data, epd_platform)

    @property
    def arch(self):
        """
        Egg architecture.
        """
        if self._epd_legacy_platform is None:
            return None
        else:
            return self._epd_legacy_platform.arch._legacy_name

    @property
    def egg_name(self):
        """
        Full egg name (including .egg extension).
        """
        return egg_name(self.name, self.version, self.build)

    @property
    def osdist(self):
        if self._epd_legacy_platform is None:
            return None
        else:
            return self._epd_legacy_platform.osdist

    @property
    def platform(self):
        """
        The legacy platform name (sys.platform).
        """
        if self._epd_legacy_platform is None:
            return None
        else:
            return self._epd_legacy_platform.platform

    @property
    def metadata_version(self):
        return self._metadata_version

    @metadata_version.setter
    def metadata_version(self, value):
        self._metadata_version = value

    def _to_dict(self):
        raw_data = {
            _TAG_NAME: self.name,
            _TAG_VERSION: self.version,
            _TAG_BUILD: self.build,
            _TAG_ARCH: self.arch,
            _TAG_PLATFORM: self.platform,
            _TAG_OSDIST: self.osdist,
            _TAG_PACKAGES: [str(p) for p in self.packages],
            _TAG_PYTHON: self.python,
            _TAG_PYTHON_PEP425_TAG: self.python_tag,
            _TAG_ABI_PEP425_TAG: self.abi_tag,
            _TAG_PLATFORM_PEP425_TAG: self.platform_tag,
            _TAG_METADATA_VERSION: self.metadata_version
        }

        ret = {}
        for k, v in raw_data.items():
            if isinstance(v, string_types):
                v = str(v)
            ret[k] = v
        return ret

    def to_string(self):
        """
        Returns a string that is suitable for the depend file inside our
        legacy egg.
        """
        template = _METADATA_TEMPLATES.get(self.metadata_version, None)
        data = self._to_dict()

        # This is just to ensure the exact same string as the produced by the
        # legacy buildsystem
        if len(self.packages) == 0:
            data[_TAG_PACKAGES] = "[]"
        else:
            data[_TAG_PACKAGES] = "[\n{0}\n]". \
                format("\n".join("  '{0}',".format(p)
                       for p in self.packages))
        return template.format(**data)

class Dependencies(object):
    """ Object storing the various dependencies for an egg.

    Each attribute is a tuple of Requirement instances.
    """
    def __init__(self, runtime=None, build=None):
        self.runtime = runtime or ()
        self.build = runtime or ()

_TAG_RE = re.compile("""
    (?P<interpreter>(cp|pp|cpython|py))
    (?P<version>([\d_]+))
""", flags=re.VERBOSE)

def _python_tag_to_python(python_tag):
    # This converts only python version we currently intent to support in
    # metadata version 1.x.
    if python_tag is None:
        return None

    generic_exc = InvalidMetadataField('python_tag', python_tag)

    m = _TAG_RE.match(python_tag)
    if m is None:
        raise generic_exc
    else:
        d = m.groupdict()
        version = d["version"]
        if len(version) == 1:
            if version == "2":
                return "2.7"
            else:
                raise generic_exc
        elif len(version) == 2:
            return "{0}.{1}".format(version[0], version[1])
        else:
            raise generic_exc

def _metadata_version_to_tuple(metadata_version):
    """ Convert a metadata version string to a tuple for comparison."""
    return tuple(int(s) for s in metadata_version.split("."))

def _guess_abi_tag(platform, python_tag):
    assert python_tag is not None, "BUG, this function expects a python_tag"

    # For legacy (aka legacy spec version info < 1.3), we know that pyver
    # can only be one of "2.X" with X in (5, 6, 7).
    #
    # In those cases, the mapping (platform pyver) -> ABI is unambiguous,
    # as we only ever used one ABI for a given python version/platform.
    pyver = _python_tag_to_python(python_tag)
    return "cp{0}{1}m".format(pyver[0], pyver[2])

def _guess_platform_tag(platform):
    if platform is None:
        return None

    return platform.pep425_tag

def _normalized_info_from_string(spec_depend_string, epd_platform=None,
                                 sha256=None):
    """ Return a 'normalized' dictionary from the given spec/depend string.

    Note: the name value is NOT lower-cased, so that the egg filename may
    rebuilt from the data.
    """
    raw_data = parse_rawspec(spec_depend_string)

    data = {}
    for k in (_TAG_METADATA_VERSION,
              _TAG_NAME, _TAG_VERSION, _TAG_BUILD,
              _TAG_ARCH, _TAG_OSDIST, _TAG_PLATFORM,
              _TAG_PYTHON, _TAG_PACKAGES):
        data[k] = raw_data[k]

    epd_platform = epd_platform or _epd_platform_from_raw_spec(data)
    for k in (_TAG_ARCH, _TAG_PLATFORM, _TAG_OSDIST):
        data.pop(k)

    metadata_version = MetadataVersion.from_string(data[_TAG_METADATA_VERSION])

    python_tag = EGG_PYTHON_TAG_BLACK_LIST.get(sha256)
    if python_tag:
        data[_TAG_PYTHON_PEP425_TAG] = python_tag
    else:
        if metadata_version < M("1.2"):
            data[_TAG_PYTHON_PEP425_TAG] = _guess_python_tag(
                raw_data[_TAG_PYTHON]
            )
        else:
            data[_TAG_PYTHON_PEP425_TAG] = raw_data[_TAG_PYTHON_PEP425_TAG]

    if metadata_version < M("1.3"):
        python_tag = data[_TAG_PYTHON_PEP425_TAG]

        if python_tag is None:
            # No python tag, so should only be a "pure binary" egg, i.e.
            # an egg containing no python code and no python C extensions.
            abi = None
        elif epd_platform is None:
            # No platform, so should only be a "pure python" egg, i.e.
            # an egg containing no C extension.
            abi = None
        else:
            abi = _guess_abi_tag(epd_platform, python_tag)
        data[_TAG_ABI_PEP425_TAG] = abi
    else:
        data[_TAG_ABI_PEP425_TAG] = raw_data[_TAG_ABI_PEP425_TAG]

    if metadata_version < M("1.3"):
        if epd_platform is None:
            platform_tag = None
        else:
            platform_tag = _guess_platform_tag(epd_platform)
        data[_TAG_PLATFORM_PEP425_TAG] = platform_tag
    else:
        data[_TAG_PLATFORM_PEP425_TAG] = raw_data[_TAG_PLATFORM_PEP425_TAG]

    return data, epd_platform

class EggMetadata(object):
    """ Enthought egg metadata for format 1.x.
    """

    HIGHEST_SUPPORTED_METADATA_VERSION = _METADATA_DEFAULT_VERSION
    """ Highest supported metadata version (as a MetadataVersion object).

    If the parsed metadata is higher, it will not be possible to write back
    the metadata. If the parsed metadata version is not compatible (different
    major version), then parsing will raise an UnsupportedMetadata exception as
    well.
    """

    @classmethod
    def from_egg(cls, path_or_file, strict=True):
        """ Create a EggMetadata instance from an existing Enthought egg.

        Parameters
        ----------
        path: str or file-like object.
            If a string, understood as the path to the egg. Otherwise,
            understood as a zipfile-like object.
        strict: bool
            If True, will fail if metadata cannot be decoded correctly (e.g.
            unicode errors in EGG-INFO/PKG-INFO). If false, will ignore those
            errors, at the risk of data loss.
        """
        sha256 = None
        if isinstance(path_or_file, string_types):
            if (
                may_be_in_platform_blacklist(path_or_file)
                or may_be_in_pkg_info_blacklist(path_or_file)
                or may_be_in_python_tag_blacklist(path_or_file)
            ):
                sha256 = compute_sha256(path_or_file)
        else:
            with _keep_position(path_or_file.fp):
                sha256 = compute_sha256(path_or_file.fp)
        return cls._from_egg(path_or_file, sha256, strict)

    @classmethod
    def _from_egg(cls, path_or_file, sha256, strict=True):
        def _read_summary(fp):
            summary_arcname = "EGG-INFO/spec/summary"
            try:
                summary = fp.read(summary_arcname)
            except KeyError:
                # the summary file may not exist for eggs built with
                # endist/repack
                summary = b""
            return summary.decode("utf8")

        spec_depend = LegacySpecDepend._from_egg(path_or_file, sha256)

        if isinstance(path_or_file, string_types):
            with zipfile2.ZipFile(path_or_file) as fp:
                summary = _read_summary(fp)
                pkg_info_data = _read_pkg_info(fp)
        else:
            summary = _read_summary(path_or_file)
            pkg_info_data = _read_pkg_info(path_or_file)

        if pkg_info_data is None:
            pkg_info = None
        else:
            pkg_info = PackageInfo._from_egg(path_or_file, sha256, strict)

        return cls._from_spec_depend(spec_depend, pkg_info, summary)

    @classmethod
    def _from_spec_depend(cls, spec_depend, pkg_info, summary,
                          metadata_version=None):
        raw_name = spec_depend.name

        version = EnpkgVersion.from_upstream_and_build(spec_depend.version,
                                                       spec_depend.build)

        python_tag = spec_depend.python_tag
        abi_tag = spec_depend.abi_tag

        if spec_depend._epd_legacy_platform is None:
            platform = None
        else:
            platform_string = str(spec_depend._epd_legacy_platform)
            platform = EPDPlatform.from_epd_string(platform_string)

        dependencies = Dependencies(
            tuple(dep for dep in spec_depend.packages)
        )

        metadata_version = metadata_version or spec_depend.metadata_version

        return cls(raw_name, version, platform, python_tag, abi_tag,
                   dependencies, pkg_info, summary, metadata_version)

    @classmethod
    def from_egg_metadata(cls, egg_metadata, **kw):
        """ Utility ctor to create a new EggMetadata instance from an existing
        one, potentially updating some metadata.

        Any keyword argument (except `egg_metadata`) is understood as an
        argument to EggMetadata.__init__.

        Parameters
        ----------
        egg_metadata: EggMetadata
        """
        passed_kw = {"raw_name": egg_metadata._raw_name}

        for k in (
            "version", "platform", "python", "abi_tag", "pkg_info", "summary",
            "metadata_version"
        ):
            passed_kw[k] = getattr(egg_metadata, k)
        passed_kw["dependencies"] = Dependencies(
            egg_metadata.runtime_dependencies
        )

        passed_kw.update(**kw)

        return cls(**passed_kw)

    def __init__(self, raw_name, version, platform, python, abi_tag,
                 dependencies, pkg_info, summary, metadata_version=None):
        """ EggMetadata instances encompass Enthought egg metadata.

        Note: the constructor is considered private, please use one of the
        from_* class methods.

        Parameters
        ----------
        raw_name: str
            The 'raw' name, i.e. the name value in spec/depend.
        version: EnpkgVersion
            The full version
        platform: EPDPlatform
            An EPDPlatform instance, or None for cross-platform eggs
        python: Python
            The python implementation
        abi_tag: str
            The ABI tag, e.g. 'cp27m'. May be None.
        dependencies: Dependencies
            A Dependencies instance.
        pkg_info: PackageInfo or None
            Instance modeling the PKG-INFO content of the egg.
        summary: str
            The summary. Models the string in EGG-INFO/spec/summary. May
            be empty.
        """
        self._raw_name = raw_name
        self.version = version
        """ The version, as an EnpkgVersion instance."""

        self.platform = platform
        """ The platform, as a Platform instance."""

        if isinstance(python, string_types):
            python = PythonImplementation.from_string(python)
        self.python = python
        """ The python implementation."""

        self.abi_tag = abi_tag
        """ The ABI tag, following the PEP425 format, except that no ABI
        is sorted as None."""

        self.runtime_dependencies = tuple(dependencies.runtime)
        """ List of runtime dependencies (as strings)."""

        self.metadata_version = metadata_version or _METADATA_DEFAULT_VERSION
        """ The version format of the underlying metadata."""

        self.pkg_info = pkg_info
        """ A PackageInfo instance modeling the underlying PKG-INFO. May
        be None for eggs without an PKG-INFO file."""

        self.summary = summary
        """ The summary string."""

    @property
    def abi_tag_string(self):
        if self.abi_tag is None:
            return 'none'
        else:
            return self.abi_tag

    @property
    def build(self):
        """ The build number."""
        return self.version.build

    @property
    def egg_basename(self):
        """ The egg "base name", i.e. the name part of the egg filename."""
        return self._raw_name

    @property
    def egg_name(self):
        """ The egg filename."""
        return self._spec_depend.egg_name

    @property
    def is_strictly_supported(self):
        """ Returns True if the given metadata_version is fully supported.

        A metadata_version is fully supported iff:
            - metadata_version.major ==
              EggMetadata.HIGHEST_SUPPORTED_METADATA_VERSION.major
            - and metadata_version.minor <=
              EggMetadata.HIGHEST_SUPPORTED_METADATA_VERSION.minor
        """
        max_supported = EggMetadata.HIGHEST_SUPPORTED_METADATA_VERSION
        return (
            _are_compatible(self.metadata_version, max_supported) and
            self.metadata_version.minor <= max_supported.minor
        )

    @property
    def kind(self):
        return "egg"

    @property
    def name(self):
        """ The package name."""
        return self._raw_name.lower().replace("-", "_")

    @property
    def platform_tag(self):
        """ Platform tag following PEP425, except that no platform is
        represented as None and not 'any'."""
        if self.platform is None:
            return None
        else:
            return self.platform.pep425_tag

    @property
    def platform_tag_string(self):
        if self.platform_tag is None:
            return 'any'
        else:
            return self.platform_tag

    @property
    def python_tag(self):
        if self.python is None:
            return None
        else:
            return self.python.pep425_tag

    @property
    def python_tag_string(self):
        if self.python_tag is None:
            # an extension of PEP 425, to signify the egg will work on any
            # python version (mostly non-python eggs)
            return 'none'
        else:
            return self.python_tag

    @property
    def spec_depend_string(self):
        return self._spec_depend.to_string()

    @property
    def upstream_version(self):
        return str(self.version.upstream)

    @property
    def _python(self):
        if self.python is None:
            return None
        else:
            return "{0}.{1}".format(self.python.major, self.python.minor)

    @property
    def _spec_depend(self):
        if not self.is_strictly_supported:
            msg = "Cannot write back metadata with unsupported version {0!r}"
            raise UnsupportedMetadata(
                self.metadata_version, msg.format(str(self.metadata_version))
            )

        if self.platform is None:
            _epd_legacy_platform = None
        else:
            _epd_legacy_platform = LegacyEPDPlatform(self.platform)

        args = {
            "name": self._raw_name,
            "version": self.upstream_version,
            "build": self.build,
            "python": self._python,
            "python_tag": self.python_tag,
            "abi_tag": self.abi_tag,
            "platform_tag": self.platform_tag,
            "packages": [p for p in self.runtime_dependencies],
            "_epd_legacy_platform": _epd_legacy_platform,
            "_metadata_version": self.metadata_version,
        }
        return LegacySpecDepend(**args)


#!/usr/bin/env python
# -*- coding: utf-8 -*-
import datetime
import my_models
import mac_slideshow

"""
Вода каждый час
http://www.edabezvreda.ru/topic-146.html
"""

SLIDESHOW = "~/Library/Screen Savers/water"

class Water(my_models.Task):
    title = "Вода"
    alias_name = "Выпейте воды"

    @property
    def next(self):
        now = datetime.datetime.now()
        next = now
        if self.latest:
            next = next if next > now else now
        return next

    @property
    def todo(self):
        return not self.latest or self.latest.hours > 0

    def run(self):
        self.save().rmalias()
        self.say("water/", background=True)
        mac_slideshow.restart(SLIDESHOW)
        while mac_slideshow.pid():
            self.sleep(0.1)

if __name__ == "__main__":
    Water().run()


""" CustomerDetailsPanel.py

    The CustomerDetailsPanel accepts the following information:

        Customer
        Caller's Name
        Caller's Phone Number

    It notifies the editor when the following field value is changed:

        customer
        callersName
        callersPhoneNumber

    NOTE: At present, we temporarily use the "jobCallersPhoneNumber1" field as
          the default contact phone number.  This field name will change
          shortly to "jobDefaultPhoneNumber", and the "jobCallersPhoneNumber2"
          and "jobCallersPhoneNumber3" fields will be removed.
"""
import wx

DateUtils      = Framework.get("library.utilities.DateUtils")

Database         = Framework.get("shared.Database")
InputPanel       = Framework.get("shared.Editor").InputPanel
LinkInputField   = Framework.get("shared.Editor").LinkInputField
ChoiceInputField = Framework.get("shared.Editor").ChoiceInputField
TextInputField   = Framework.get("shared.Editor").TextInputField

CustomerInputField = Framework.get("shared.Editor.jobInputFields." +
                                   "CustomerInputField").CustomerInputField

#############################################################################

class CustomerDetailsPanel(InputPanel):
    """ The "Customer Details" input panel.
    """
    def __init__(self, parent, editor):
        """ Standard initialiser.

            'editor' is the InputEditor this panel belongs to.
        """
        InputPanel.__init__(self, parent)
        self._editor = editor

        self._customerField = CustomerInputField(self, displayWidth=36)

        self._callersNameField = ChoiceInputField(self,
                                                  True,
                                                  True,
                                                  "Caller's Name",
                                                  "Caller's Name",
                                                  displayWidth=20,
                                                  editWidth=32)

        self._callersPhoneField = TextInputField(self,
                                                 "Caller's Phone",
                                                 "Default Phone Number",
                                                 displayWidth=14,
                                                 editWidth=32)

        self.addField(None, self._customerField, 'customer')
        self.startRow()
        self.addField(None, self._callersNameField,  "callersName")
        self.addField(None, self._callersPhoneField, "callersPhoneNumber")
        self.endRow()

        self.layout()

        self._customerField.addListener(self.customerChanged)
        self._callersNameField.addListener(self.callersNameChanged)
        self._callersPhoneField.addListener(self.callersPhoneNumberChanged)

    def customerChanged(self, field, value):
        """ Respond to the user identifying a customer.

            We set the callers name and callers phone number fields to match
            the details of the selected customer.
        """
        if value not in [None, -1]:
            results = Database.select("Customer", ["jobCallersName1",
                                                   "jobCallersName2",
                                                   "jobCallersName3",
                                                   "jobCallersPhoneNumber1"],
                                      "id="+str(value))
        else:
            results = []

        if len(results) == 1:
            cust = results[0]

            choices = []
            for fieldNum in ['1', '2', '3']:
                if cust["jobCallersName"+fieldNum] != None:
                    choices.append(cust["jobCallersName"+fieldNum])

            self._callersNameField.setChoices(choices)
            self._callersPhoneField.setValue(cust['jobCallersPhoneNumber1'])
            self._editor.fieldChanged("customer", value)
            self._editor.fieldChanged("callersPhoneNumber",
                                      cust['jobCallersPhoneNumber1'])
        else:
            self._callersNameField.setChoices([])
            self._callersNameField.setValue("")
            self._callersPhoneField.setValue("")
            self._editor.fieldChanged("customer", None)

    def callersNameChanged(self, field, value):
        """ Respond to the user entering a value into our "callers name" field.
        """
        self._editor.fieldChanged("callersName", value)

    def callersPhoneNumberChanged(self, field, value):
        """ Respond to the user entering a value into our "phone number" field.
        """
        self._editor.fieldChanged("callersPhoneNumber", value)


# Scrapy settings for dirbot project

SPIDER_MODULES = ['dirbot.spiders']
NEWSPIDER_MODULE = 'dirbot.spiders'
DEFAULT_ITEM_CLASS = 'dirbot.items.Website'

ITEM_PIPELINES = {'dirbot.pipelines.FilterWordsPipeline': 1}
DOWNLOADER_MIDDLEWARES ={
    'scrapy.downloadermiddlewares.httpproxy.HttpProxyMiddleware': 110,
    'dirbot.middlewares.ProxyMiddleware': 100,
}


import re
import os

def remove_pagenos():
    os.chdir('cases/')
    list_dir = os.listdir('.')

    for filename in list_dir:
        f = open(filename, 'r')
        temp = f.read()
        f.close()

        g = re.findall(r'\n+.+?Page.+?\n', temp)
        for elem in g:
            temp.replace(elem, " ")
        w = open(filename + 'clean', 'w')
        w.write(temp)
        w.close()

if __name__ == '__main__':
    remove_pagenos()


import serial
import time
# ser = serial.Serial('/dev/tty.usbserial-14310', 9600, timeout=1.0)
ser = serial.Serial('/dev/tty.usbserial-14310', 115200, timeout=0.1)

result = 1
for i in range(256):
  # txd = bytes(i)
  txd = i.to_bytes(1, 'big')
  ser.write(b's') # switch
  ser.write(b'0')  # none
  ser.write(b'0')  # none
  rxd = ser.read(1)
  rdata = int.from_bytes(rxd, 'big')
  print("SW_IN: {0:d}".format(rdata))
  time.sleep(2) # 1ms


"""
Project: ChordNet
Author: Gabriel Abrantes
Email: gabrantes99@gmail.com
Date: 7/2/2019
Filename: aug.py
Description:
    Contains functions for augmenting the dataeset.
"""

from utils.chorus.satb import Satb
from utils.utils import one_hot
import numpy as np

def augment(progression: list) -> list:
    """
    `Augment` a progression by tranposing it to a new key.

    Args:
        progression: a list representing one line from the dataset
                    (with scaled chords and one-hot tuples)

    Returns:
        A list representing the new, tranposed progression
        (still with scaled chords and one-hot tuples).
    """
    assert len(progression) == 16

    satb = Satb()
    lo, hi = satb.transpose_range(satb.unscale(progression[5:9]), satb.unscale(progression[12:16]))
    shift = np.random.randint(lo, hi+1)

    new_progression = progression[:]

    # tonic (key signature)
    cur_key = new_progression[0].index(1)
    new_key = (cur_key + shift) % 12
    new_progression[0] = tuple(one_hot(new_key, 12))

    for j in range(5, 9):  # cur chord
        new_progression[j] += shift
    for j in range(12, 16):  # next chord
        new_progression[j] += shift

    return new_progression


import pytest

from optuna.trial import FixedTrial

def test_suggest_float() -> None:

    trial = FixedTrial({"x": 1.0})
    assert trial.suggest_float("x", -100.0, 100.0) == 1.0

    with pytest.raises(ValueError):
        trial.suggest_float("x", -100, 100, step=10, log=True)

    with pytest.raises(ValueError):
        trial.suggest_uniform("y", -100.0, 100.0)

def test_suggest_uniform() -> None:

    trial = FixedTrial({"x": 1.0})
    assert trial.suggest_uniform("x", -100.0, 100.0) == 1.0

    with pytest.raises(ValueError):
        trial.suggest_uniform("y", -100.0, 100.0)

def test_suggest_loguniform() -> None:

    trial = FixedTrial({"x": 0.99})
    assert trial.suggest_loguniform("x", 0.1, 1.0) == 0.99

    with pytest.raises(ValueError):
        trial.suggest_loguniform("y", 0.0, 1.0)

def test_suggest_discrete_uniform() -> None:

    trial = FixedTrial({"x": 0.9})
    assert trial.suggest_discrete_uniform("x", 0.0, 1.0, 0.1) == 0.9

    with pytest.raises(ValueError):
        trial.suggest_discrete_uniform("y", 0.0, 1.0, 0.1)

def test_suggest_int() -> None:

    trial = FixedTrial({"x": 1})
    assert trial.suggest_int("x", 0, 10) == 1

    with pytest.raises(ValueError):
        trial.suggest_int("y", 0, 10)

def test_suggest_int_log() -> None:

    trial = FixedTrial({"x": 1})
    assert trial.suggest_int("x", 1, 10, log=True) == 1

    with pytest.raises(ValueError):
        trial.suggest_int("x", 1, 10, step=2, log=True)

    with pytest.raises(ValueError):
        trial.suggest_int("y", 1, 10, log=True)

def test_suggest_categorical() -> None:

    # Integer categories.
    trial = FixedTrial({"x": 1})
    assert trial.suggest_categorical("x", [0, 1, 2, 3]) == 1

    with pytest.raises(ValueError):
        trial.suggest_categorical("y", [0, 1, 2, 3])

    # String categories.
    trial = FixedTrial({"x": "baz"})
    assert trial.suggest_categorical("x", ["foo", "bar", "baz"]) == "baz"

    # Unknown parameter.
    with pytest.raises(ValueError):
        trial.suggest_categorical("y", ["foo", "bar", "baz"])

    # Not in choices.
    with pytest.raises(ValueError):
        trial.suggest_categorical("x", ["foo", "bar"])

    # Unknown parameter and bad category type.
    with pytest.warns(UserWarning):
        with pytest.raises(ValueError):  # Must come after `pytest.warns` to catch failures.
            trial.suggest_categorical("x", [{"foo": "bar"}])  # type: ignore

def test_not_contained_param() -> None:
    trial = FixedTrial({"x": 1.0})
    with pytest.warns(UserWarning):
        assert trial.suggest_float("x", 10.0, 100.0) == 1.0

    trial = FixedTrial({"x": 1.0})
    with pytest.warns(UserWarning):
        assert trial.suggest_float("x", 10.0, 100.0, log=True) == 1.0

    trial = FixedTrial({"x": 1.0})
    with pytest.warns(UserWarning):
        assert trial.suggest_float("x", 10.0, 100.0, step=1.0) == 1.0

    trial = FixedTrial({"x": 1})
    with pytest.warns(UserWarning):
        assert trial.suggest_int("x", 10, 100) == 1

    trial = FixedTrial({"x": 1})
    with pytest.warns(UserWarning):
        assert trial.suggest_int("x", 10, 100, 1) == 1

    trial = FixedTrial({"x": 1})
    with pytest.warns(UserWarning):
        assert trial.suggest_int("x", 10, 100, log=True) == 1

def test_user_attrs() -> None:

    trial = FixedTrial({"x": 1})
    trial.set_user_attr("data", "MNIST")
    assert trial.user_attrs["data"] == "MNIST"

def test_system_attrs() -> None:

    trial = FixedTrial({"x": 1})
    trial.set_system_attr("system_message", "test")
    assert trial.system_attrs["system_message"] == "test"

def test_params() -> None:

    params = {"x": 1}
    trial = FixedTrial(params)
    assert trial.params == {}

    assert trial.suggest_uniform("x", 0, 10) == 1
    assert trial.params == params

def test_report() -> None:

    # FixedTrial ignores reported values.
    trial = FixedTrial({})
    trial.report(1.0, 1)
    trial.report(2.0, 2)

def test_should_prune() -> None:

    # FixedTrial never prunes trials.
    assert FixedTrial({}).should_prune() is False

def test_datetime_start() -> None:

    params = {"x": 1}
    trial = FixedTrial(params)
    assert trial.datetime_start is not None

def test_number() -> None:

    params = {"x": 1}
    trial = FixedTrial(params, 2)
    assert trial.number == 2

    trial = FixedTrial(params)
    assert trial.number == 0