"""
    @author: Torsten Fietzek

    Class to control the environment in the gazebo simulator, for example simple object manipulation.
    The basis is given for object manipulation in: http://www.icub.org/software_documentation/icub_python_simworld_control.html (Marek Rucinski).
    This is adapted to the gazebo simulator.
"""

import numpy as np
import yarp

yarp.Network.init()  # Initialise YARP

####################################################
"""
    TODO:
        attach
        detach
"""

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

class WorldController:
    """Class for controlling iCub simulator via its RPC world port."""

    def __init__(self):
        self._rpc_client = yarp.RpcClient()
        self._port_name = "/WorldController_" + str(id(self)) + "/commands"
        self._rpc_client.open(self._port_name)
        self._rpc_client.addOutput("/world_input_port")

    def _execute(self, cmd):
        """Execute an RPC command, returning obtained answer bottle."""
        ans = yarp.Bottle()
        self._rpc_client.write(cmd, ans)
        return ans

    def _is_success(self, ans):
        """Check if RPC call answer Bottle indicates successfull execution."""
        return ans.size() == 1 and ans.get(0).asVocab32() == 27503  # Vocab for '[ok]'

    def _prepare_del_all_command(self):
        """Prepare the "world del all" command bottle."""
        result = yarp.Bottle()
        result.clear()
        list(map(result.addString, ["deleteAll"]))
        return result

    def del_all(self):
        """Delete all objects and models from the simulator"""
        result = self._is_success(self._execute(
            self._prepare_del_all_command()))

        return result

########################################################
# object creation and manipulation
########################################################

    ########################################################
    # create object inside the simulator
    def _prepare_create_obj_command(self, obj_func, size, location, orientation, color):
        """
            Prepare an RPC command for creating an object in the gazebo simulator environment.

            See Simulator Readme section 'Object Creation'

            Parameters:
                obj_func    -- function name for the creation of the different objects: 'sphere', 'box', 'cylinder'.
                size        -- list of values specifying the size of an object. Parameters depend on object type:
                                (s)box: [ x, y, z ]
                                (s)sph: [ radius ]
                                (s)cyl: [ radius, length ]
                location    -- coordinates of the object location, [ x, y, z ]
                orientation -- object rotation in rad
                color       -- object color in RGB (values: [0...255]), [ r, g, b ]

            Returns:
                yarp.Bottle with the command, ready to be sent to the rpc port of the simulator
        """
        yarp_cmd = yarp.Bottle()
        yarp_cmd.clear()
        yarp_cmd.addString(obj_func)
        list(map(yarp_cmd.addFloat64, size))
        list(map(yarp_cmd.addFloat64, location))
        list(map(yarp_cmd.addFloat64, orientation))
        list(map(yarp_cmd.addInt32, color))
        return yarp_cmd

    def create_object(self, obj, size, location, orientation, color):
        """
            Create an object of a specified type, size, location, orientation and color, returning internal object ID or -1 on error.

            Parameters:
                obj         -- object type string: 'sphere', 'box', 'cylinder', frame.
                size        -- list of values specifying the size of an object. Parameters depend on object type:
                                box: [ x, y, z ]
                                sphere: [ radius ]
                                cylinder: [ radius, length ]
                                frame: [ scale ]
                location    -- coordinates of the object location, [ x, y, z ]
                orientation -- object rotation in rad
                color       -- object color in RGB (values: [0...255]), [ r, g, b ]

            Returns:
                object name as string; returns "" at error
        """
        if obj == "sphere":
            obj_func = "makeSphere"
        elif obj == "box":
            obj_func = "makeBox"
        elif obj == "cylinder":
            obj_func = "makeCylinder"
        elif obj == "frame":
            obj_func = "makeFrame"
        else:
            print("Error: No correct object identifier given!")
            return -1

        cmd = self._prepare_create_obj_command(
            obj_func, size, location, orientation, color)
        ans = self._execute(cmd)
        if ans.toString() == "[fail]":
            print('Error: Object creation failed!')
            return None  # error

        return ans.toString()

    ########################################################
    # set object pose inside the simulator
    def _prepare_set_pose_command_obj(self, obj_id, location, rotation):
        """
            Prepare the "world set <obj> <xyz>" command bottle.

            Parameters:
                obj         -- object descriptor string
                obj_id      -- simulator object ID
                location    -- coordinates of the object location, [ x, y, z ]
                orientation -- object rotation in rad

            Returns:
                yarp.Bottle with the command, ready to be sent to the rpc port of the simulator
        """
        yarp_cmd = yarp.Bottle()
        yarp_cmd.clear()
        yarp_cmd.addString("setPose")
        yarp_cmd.addString(obj_id)
        list(map(yarp_cmd.addFloat64, location))
        list(map(yarp_cmd.addFloat64, rotation))
        return yarp_cmd

    def set_pose(self, obj_id, location, rotation):
        """
            Set a new pose (location and rotation) for the given object.

            Parameters:
                obj_id      -- internal model ID to reference the object
                location    -- coordinates of the object location, [ x, y, z ]
                orientation -- object rotation in rad

            Returns:
                True/False dependent on success/failure.
        """
        return self._is_success(self._execute(self._prepare_set_pose_command_obj(obj_id, location, rotation)))

    ########################################################
    # get object pose inside the simulator
    def _prepare_get_pose_command_obj(self, obj_id):
        """
            Prepare the "world get <obj> <id>" command bottle.

            Parameters:
                obj         -- object descriptor string
                obj_id      -- simulator object ID

            Returns:
                yarp.Bottle with the command, ready to be sent to the rpc port of the simulator
        """
        result = yarp.Bottle()
        result.clear()
        list(map(result.addString, ["getPose", obj_id]))
        return result

    def get_pose(self, obj_id):
        """
            Obtain the object location from the simulator.

            Parameters:
                obj_id      -- internal object ID

            Returns:
                object location coordinates [x, y, z]; None on failure.

        """
        result = self._execute(self._prepare_get_pose_command_obj(obj_id))
        if result.size() == 6:
            # 6-element list with xyz coordinates
            return np.array([result.get(i).asDouble() for i in range(6)])

        return None  # An error occured

    ########################################################
    # set object name inside the simulator
    def _prepare_set_name_command_obj(self, obj_id, new_name):
        """
            Prepare the "world set <obj> <xyz>" command bottle.

            Parameters:
                obj_id      -- simulator object ID
                new_name    --

            Returns:
                yarp.Bottle with the command, ready to be sent to the rpc port of the simulator
        """
        yarp_cmd = yarp.Bottle()
        yarp_cmd.clear()
        list(map(yarp_cmd.addString, ["rename", obj_id, new_name]))
        return yarp_cmd

    def set_name(self, obj_id, new_name):
        """
            !!! not fully implemented in gazebo !!!
            Set a new name for the given object.

            Parameters:
                obj_id      -- internal model ID to reference the object
                new_name    -- new model name

            Returns:
                True/False dependent on success/failure.
        """
        result = self._is_success(self._execute(
            self._prepare_set_name_command_obj(obj_id, new_name)))
        return result, new_name

    ########################################################
    # change object color inside the simulator
    def _prepare_change_color_command_obj(self, obj_id, new_color):
        """
            Prepare the "world set <obj> <xyz>" command bottle.

            Parameters:
                obj_id      -- simulator object ID
                new_name    --

            Returns:
                yarp.Bottle with the command, ready to be sent to the rpc port of the simulator
        """
        yarp_cmd = yarp.Bottle()
        yarp_cmd.clear()
        list(map(yarp_cmd.addString, ["changeColor", obj_id]))
        list(map(yarp_cmd.addInt32, new_color))

        return yarp_cmd

    def change_color(self, obj_id, new_color):
        """
            !!! not fully implemented in plugins!!!
            Set a new color for the given object.

            Parameters:
                obj_id      -- internal model ID to reference the object
                new_color   --

            Returns:
                True/False dependent on success/failure.
        """
        return self._is_success(self._execute(self._prepare_change_color_command_obj(obj_id, new_color)))

    ########################################################
    # enable gravity for the given obejct
    def _prepare_enable_gravity_command_obj(self, obj_id, enable):
        """
            Prepare the "world set <obj> <xyz>" command bottle.

            Parameters:
                obj_id      -- internal model ID to reference the object
                enable      -- Integer -> >0 enable gravity; <=0 disable gravity

            Returns:
                yarp.Bottle with the command, ready to be sent to the rpc port of the simulator
        """
        yarp_cmd = yarp.Bottle()
        yarp_cmd.clear()
        yarp_cmd.addString("enableGravity")
        yarp_cmd.addString(obj_id)
        yarp_cmd.addInt32(enable)
        return yarp_cmd

    def enable_gravity(self, obj_id, enable):
        """
            Enable gravity for the given object.

            Parameters:
                obj_id      -- internal model ID to reference the object
                enable      -- Integer -> >0 enable gravity; <=0 disable gravity

            Returns:
                True/False dependent on success/failure.
        """
        return self._is_success(self._execute(self._prepare_enable_gravity_command_obj(obj_id, enable)))

    ########################################################
    # enable collision for the given obejct
    def _prepare_enable_collision_command_obj(self, obj_id, enable):
        """
            Prepare the command bottle.

            Parameters:
                obj_id      -- internal model ID to reference the object
                enable      -- Integer -> >0 enable collision; <=0 disable collision

            Returns:
                yarp.Bottle with the command, ready to be sent to the rpc port of the simulator
        """
        yarp_cmd = yarp.Bottle()
        yarp_cmd.clear()
        yarp_cmd.addString("enableCollision")
        yarp_cmd.addString(obj_id)
        yarp_cmd.addInt32(enable)
        return yarp_cmd

    def enable_collision(self, obj_id, enable):
        """
            Enable collision for the given object.

            Parameters:
                obj_id      -- internal model ID to reference the object
                enable      -- Integer -> >0 enable collision; <=0 disable collision

            Returns:
                True/False dependent on success/failure.
        """
        return self._is_success(self._execute(self._prepare_enable_collision_command_obj(obj_id, enable)))

    ########################################################
    # get list with object names
    def _prepare_get_list_command_obj(self):
        """
            Prepare the command bottle.

            Parameters:

            Returns:
                yarp.Bottle with the command, ready to be sent to the rpc port of the simulator
        """
        result = yarp.Bottle()
        result.clear()
        result.addString("getList")
        return result

    def get_list(self):
        """
            Return a list with the names of the objects created inside the simulator.

            Parameters:

            Returns:
                list with strings of object names; None on failure.

        """
        result = self._execute(self._prepare_get_list_command_obj())
        if result.size() < 1 or result.get(0).toString() == "[fail]":
            return None  # An error occured

        return result.pop().toString().split(' ')

    ########################################################
    # delete a single Object in the simulator
    def _prepare_del_object_command_obj(self, obj_id):
        """
            Prepare the "world get <obj> <id>" command bottle.

            Parameters:
                obj         -- object descriptor string
                obj_id      -- simulator object ID

            Returns:
                yarp.Bottle with the command, ready to be sent to the rpc port of the simulator
        """
        result = yarp.Bottle()
        result.clear()
        list(map(result.addString, ["deleteObject", obj_id]))
        return result

    def del_object(self, obj_id):
        """
            Obtain the object location from the simulator.

            Parameters:
                obj_id      -- internal object ID

            Returns:
                True/False dependent on success/failure.

        """
        result = self._is_success(self._execute(
            self._prepare_del_object_command_obj(obj_id)))
        return result

    ########################################################
    # load 3D model into the simulator
    def _prepare_create_model_command_model(self, filename, location, orientation):
        """
            Prepare an RPC command for importing a model in the simulator environment.

            Parameters:
                filename    -- absolute path to the model
                location    -- coordinates of the object location, [ x, y, z ]
                orientation -- object rotation in rad

            Returns:
                yarp.Bottle with the command, ready to be sent to the rpc port of the simulator
        """
        result = yarp.Bottle()
        result.clear()
        result.addString("loadModelFromFileWithPose")
        result.addString(filename)
        list(map(result.addFloat64, location))
        list(map(result.addFloat64, orientation))
        return result

    def create_model(self, filename, location, orientation):
        """
            Import a model from a sdf file.

            Parameters:
                filename    -- absolute path to the model
                location    -- coordinates of the object location, [ x, y, z ]
                orientation -- object rotation in rad

            Returns:
                string as internal model ID to reference the model. In case of an error an empty string is returned
        """
        cmd = self._prepare_create_model_command_model(filename, location, orientation)
        ans = self._execute(cmd)
        if ans.toString() == "[fail]":
            print('error')
            return ""  # error

        return ans.toString()

    ########################################################
    # call destructor
    def __del__(self):
        try:
            if self._rpc_client is not None:
                self.del_all()
            self._rpc_client.close()
            del self._rpc_client
        except AttributeError:
            pass


"""
用于建立局域网用户列表的建立；
1. 当程序启动时，主动使用UDP协议向255.255.255.255端口发送广播包，默认端口为44444，
    广播包内容包括：用户名，主机名，IP，工作组；已启动飞鸽的用户通过2425端口收到此广播包后，
    就会在自己的用户列表中添加这个用户的用户名、工作组等信息，同时向对方IP发送本机用户的个人信息；
2. 如何监控44444端口的数据流，实时刷新局域网用户列表和聊天信息；解决方案问题4，
3. 用户离线时发送一个离线广播包到255.255.255.255，收到此广播包的用户，根据包中的IP地址（也可能
    是多种判断标志或者包含硬件标识，比如网卡地址等）删除对方的用户列表信息；

问题1：如何使用UDP协议向255.255.255.255：44444发送广播包；
    # 1、创建udp套接字
	# socket.AF_INET  表示IPv4协议  AF_INET6 表示IPv6协议
	# socket.SOCK_DGRAM  数据报套接字，只要用于udp协议
	udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
	# 2、准备接收方的地址
	# 元组类型  ip是字符串类型   端口号是整型
	dest_addr = ('10.10.10.10', 0000)
	# 要发送的数据
	send_data = "我是要发送的数据"
	# 3、发送数据
	udp_socket.sendto(send_data.encode("utf-8"), dest_addr)
	# 4、等待接收方发送的数据  如果没有收到数据则会阻塞等待，直到收到数据
	# 接收到的数据是一个元组   (接收到的数据, 发送方的ip和端口)
	# 1024  表示本次接收的最大字节数
	recv_data, addr = udp_socket.recvfrom(1024)
	# 5、关闭套接字
	udp_socket.close()
	###############################################

    import socket

    if __name__ == '__main__':
        # 创建socket
        udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # 设置socket选项
        udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, True)
        # 发送广播消息
        udp_socket.sendto("hello".encode("gbk"), ("255.255.255.255", 8989))
        udp_socket.close()

问题2：广播包的内容机器解析；

问题3：如何得到主机的IP和主机名称（done）
    方法1    这样可以获取到本机所有网卡的IP地址：IPs = socket.gethostbyname_ex(socket.gethostname())[-1]
            如果想获取正在上网所使用的本机IP，通过route命令可以得到：
            Windows下用 [a for a in os.popen('route print').readlines() if ' 0.0.0.0 ' in a][0].split()[-2]
            Linux下用 [a for a in os.popen('/sbin/route').readlines() if 'default' in a][0].split()[1]
    方法2
        import socket

        def get_host_ip():
            try:
                s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
                s.connect(('8.8.8.8', 80))
                ip = s.getsockname()[0]
            finally:
                s.close()

            return ip

问题4. 如何监控44444端口的数据流，实时刷新局域网用户列表和聊天信息；（done）
    “”“
    1. 监听TCP端口信息；
    2. 监听UDP端口信息；
    ”“”
    import threading
    import socket

    encoding = 'utf-8'
    BUFSIZE = 1024

    # a read thread, read data from remote
    class Reader(threading.Thread):
        def __init__(self, client):
            threading.Thread.__init__(self)
            self.client = client

        def run(self):
            while True:
                data = self.client.recv(BUFSIZE)
                if(data):
                    string = bytes.decode(data, encoding)
                    print(string, end='')
                else:
                    break
            print("close:", self.client.getpeername())

        def readline(self):
            rec = self.inputs.readline()
            if rec:
                string = bytes.decode(rec, encoding)
                if len(string)>2:
                    string = string[0:-2]
                else:
                    string = ' '
            else:
                string = False
            return string

    # a listen thread, listen remote connect
    # when a remote machine request to connect, it will create a read thread to handle
    class Listener(threading.Thread):
        def __init__(self, port):
            threading.Thread.__init__(self)
            self.port = port
            self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            self.sock.bind(("0.0.0.0", port))
            self.sock.listen(0)
        def run(self):
            print("listener started")
            while True:
                client, cltadd = self.sock.accept()
                Reader(client).start()
                cltadd = cltadd
                print("accept a connect")

    lst  = Listener(9011)   # create a listen thread
    lst.start() # then start

问题5. 如何设计存放用户信息的类（名片类）；
    usr_card#id#usr_name#host_name

"""

import socket
import threading
import time

BUFSIZE = 1024
ENCODING = 'utf-8'

class UsrCard(object):
    """
    用户名片类
    """

    def __init__(self, ip, host_name, usr_name,):
        self._ip = ip
        self._host_name = host_name
        self._usr_name = usr_name

    def __str__(self):
        return f"usr_card#{self._ip}#{self._host_name}#{self._usr_name}"

    def card_info(self):
        return f"usr_card#{self._ip}#{self._host_name}#{self._usr_name}"

    def ip(self):
        return self._ip

    def host_name(self):
        return self._host_name

    def usr_name(self):
        return self._usr_name

class Reader(threading.Thread):
    """
    读取监听UDP端口44444的数据流
    """
    def __init__(self, data, addr, main_frame):
        threading.Thread.__init__(self)
        self.data = data
        self.friend_ip = addr[0]
        print(self.friend_ip)
        # self.usr_sheet = usr_sheet
        self.main_frame = main_frame

    def run(self):

        if self.data:
            string_msg = bytes.decode(self.data, ENCODING)

            print(string_msg, f"from {self.friend_ip}")
            # 1. 将收到的信息进行分类，如果发送的是一个端口信息，则将该端口信息保存在名片中，
            if string_msg.startswith("usr_card#") and self.main_frame.usr_sheet.my_card.ip() != self.friend_ip:
                print(f"名片：{string_msg}")
                self.save_card(string_msg)
                # 当收到一个用户的卡片后我们要将我们的卡片回发给对方；
                self.send_my_card(string_msg.split("#")[1])
            elif string_msg.startswith("message#") and self.main_frame.usr_sheet.my_card.ip() != self.friend_ip:
                print("message#shoudaome")
                self.main_frame.message_list.append(string_msg)
                for chat_frame_window in self.main_frame.pop_chat_window_list:
                    print(chat_frame_window)
                    if chat_frame_window.friend_ip == self.friend_ip:
                        print("messshow")
                        chat_frame_window.ShowMsg(string_msg)

            # 同时将名片添加到名片夹中；
            # 2. 如果收到的信息是一个消息，则提醒用户并显示在响应的对话框中，并返回给发送方一个收到的消息；

    def save_card(self, string):
        usr_info = string.split("#")[1:]
        usr_card = UsrCard(*usr_info)
        self.main_frame.usr_sheet.add(usr_card)

    def send_my_card(self, friend_ip):
        my_card = self.main_frame.usr_sheet.my_card.card_info()
        udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        udp_socket.sendto(my_card.encode(ENCODING), (friend_ip, 44444))
        udp_socket.close()

class Listener(threading.Thread):
    """
    监听UDP端口44444的数据流。

    """
    def __init__(self, main_frame, port=44444):
        threading.Thread.__init__(self)
        self.port = port
        self.main_frame = main_frame
        # self.usr_sheet = main_frame.usr_sheet.usr_sheet()
        # self.message_list = main_frame.message_list
        # self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 监听TCP端口代码
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.sock.bind(("", port))
        # self.sock.listen(0)   # 监听TCP端口
        # 将自己的地址广播到局域网中
        self.broadcast()

    def run(self):
        print("listener started")
        while True:
            # client, cltadd = self.sock.accept()
            data, addr = self.sock.recvfrom(1024)
            Reader(data, addr, self.main_frame).start()
            print(f"read data:{data} from {addr}")

    def broadcast(self):
        bcast_addr = ("255.255.255.255", 44444)
        data = self.main_frame.usr_sheet.my_card.card_info()
        udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST,True)
        udp_socket.sendto(data.encode("utf-8"), bcast_addr)
        print("broadcast: ok")
        udp_socket.close()

class UserSheet(object):

    def __init__(self):
        self.ip = self.get_ip()
        self.usr_name = self.get_usr_name()
        self.host_name = self.get_host_name()
        # self.group_name = self.get_group_name()
        self.usersheet = []
        self.message_list = []
        self.my_card = UsrCard(self.ip, self.host_name, self.usr_name)
        # self.add(self.my_card)
        self.start = 0
        # self.listener = Listener()
        # self.listener.start()
        # self.broadcast()

    def usr_sheet(self):
        return self.usersheet

    def add(self, usr_card):
        # 当收到用户的名片时将其添加到用户表单中
        self.usersheet.append(usr_card)

    def remove(self, usr_card):
        # 当用户下线时从用户表单中移除用户名片
        self.usersheet.remove(usr_card)

    def __iter__(self):
        return self

    def __next__(self):
        # 迭代方法有问题，先不用。
        if self.start <= len(self.usersheet):
            self.start += 1
            return self.usersheet[self.start-1]
        else:
            raise StopIteration

    def listen_port(self):
        # udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        # udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # udp_socket.bind(("", 44444))
        # udp_socket.listen(0)
        pass

    def get_ip(self):
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(('8.8.8.8', 80))
            ip = s.getsockname()[0]
            print(ip)
        finally:
            s.close()

        return ip

    def get_usr_name(self):
        return socket.gethostname()

    def get_host_name(self):
        return socket.gethostname()

    def get_group_name(self):
        return "group_name"

if __name__ == '__main__':
    usr_sheet =UserSheet()
    Listener(usr_sheet).start()
    # UsrCard("ip","host","usr")
    while True:
        for i in usr_sheet.usersheet:
            print(i)

        time.sleep(60)


# 6.0002 Problem Set 5
# Graph optimization
# Name:
# Collaborators:
# Time:

#
# Finding shortest paths through MIT buildings
#
from csv import Dialect
from signal import SIG_DFL
import unittest
from graph import Digraph, Node, WeightedEdge

#
# Problem 2: Building up the Campus Map
#
# Problem 2a: Designing your graph
#
# What do the graph's nodes represent in this problem? What
# do the graph's edges represent? Where are the distances
# represented?
#
# Answer:
"""
Each line in the text file holds 4 numbers: (start building, end building, distance, outdoor distance)
- instanciate a new Diagraph object
- loop over the text file and for each line instanciate check if start and endbuilding
    already have been added as nodes to the graph, if not add them and second
    add the weighted edge to the start building node.
"""

# Problem 2b: Implementing load_map
def load_map(map_filename):
    digraph = Digraph()

    print("Loading map from file...")

    campus_data = open(map_filename, 'r', encoding='utf-8')
    with campus_data:
        for line in campus_data:
            array = line.split(' ')
            src_node = Node(array[0])
            dest_node = Node(array[1])
            if src_node not in digraph.edges:
                digraph.add_node(src_node)
            if dest_node not in digraph.edges:
                digraph.add_node(dest_node)
            edge = WeightedEdge(src_node, dest_node, int(array[2]), int(array[3]))
            digraph.add_edge(edge)
    print("finished")
    return digraph

# Problem 2c: Testing load_map
# Include the lines used to test load_map below, but comment them out
def test_load_map():
    digraph = load_map('test_load_map.txt')
    digraph_test = Digraph()
    node1 = Node('1')
    node2 = Node('2')
    node3 = Node('3')
    node4 = Node('4')
    digraph_test.add_node(node1)
    digraph_test.add_node(node2)
    digraph_test.add_node(node3)
    digraph_test.add_node(node4)
    edge1 = WeightedEdge(node1, node2, 70, 30)
    edge2 = WeightedEdge(node2, node3, 70, 5)
    edge3 = WeightedEdge(node1, node3, 30, 10)
    edge4 = WeightedEdge(node1, node4, 30, 10)
    digraph_test.add_edge(edge1)
    digraph_test.add_edge(edge2)
    digraph_test.add_edge(edge3)
    digraph_test.add_edge(edge4)
    return (digraph.__str__() == digraph_test.__str__())

#
# Problem 3: Finding the Shorest Path using Optimized Search Method
#
# Problem 3a: Objective function
#
# What is the objective function for this problem? What are the constraints?
#
# Answer:
"""
Goal is to find a function that returns the shortest (= least amount of meters) possible path from A to B. Constraint
might be a max. amount of meters outside
"""

# Problem 3b: Implement get_best_path
def get_best_path(digraph, start, end, path,  best_path, max_dist_outdoors, best_dist):
    """
    Finds the shortest path between buildings subject to constraints.

    Parameters:
        digraph: Digraph instance
            The graph on which to carry out the search
        start: string
            Building number at which to start
        end: string
            Building number at which to end
        path: list composed of [[list of strings], int, int]
            Represents the current path of nodes being traversed. Contains
            a list of node names, total distance traveled, and total
            distance outdoors.
        max_dist_outdoors: int
            Maximum distance spent outdoors on a path
        best_dist: int
            The smallest distance between the original start and end node
            for the initial problem that you are trying to solve
        best_path: list of strings
            The shortest path found so far between the original start
            and end node.

    Returns:
        A tuple with the shortest-path from start to end, represented by
        a list of building numbers (in strings), [n_1, n_2, ..., n_k],
        where there exists an edge from n_i to n_(i+1) in digraph,
        for all 1 <= i < k and the distance of that path.

        If there exists no path that satisfies max_total_dist and
        max_dist_outdoors constraints, then return None.
    """
    path = [path[0] + [start]]
    if len(path) < 2:
        path.append(0)
    if(start == end):
        return path
    start_node = Node(start)
    edges = digraph.get_edges_for_node(start_node)
    for edge in edges:
        start = edge.get_destination().get_name()
        if(start not in path):
            if best_path == None or path[1] <  best_dist:
                path[1] += edge.get_total_distance()
                new_path = get_best_path(digraph, edge.get_destination().get_name(), end, path, best_path, max_dist_outdoors, best_dist)
                if new_path != None:
                    best_path = new_path
    return best_path

# Problem 3c: Implement directed_dfs
def directed_dfs(digraph, start, end, max_total_dist, max_dist_outdoors):
    """
    Finds the shortest path from start to end using a directed depth-first
    search. The total distance traveled on the path must not
    exceed max_total_dist, and the distance spent outdoors on this path must
    not exceed max_dist_outdoors.

    Parameters:
        digraph: Digraph instance
            The graph on which to carry out the search
        start: string
            Building number at which to start
        end: string
            Building number at which to end
        max_total_dist: int
            Maximum total distance on a path
        max_dist_outdoors: int
            Maximum distance spent outdoors on a path

    Returns:
        The shortest-path from start to end, represented by
        a list of building numbers (in strings), [n_1, n_2, ..., n_k],
        where there exists an edge from n_i to n_(i+1) in digraph,
        for all 1 <= i < k

        If there exists no path that satisfies max_total_dist and
        max_dist_outdoors constraints, then raises a ValueError.
    """
    # TODO
    pass

# ================================================================
# Begin tests -- you do not need to modify anything below this line
# ================================================================

class Ps2Test(unittest.TestCase):
    LARGE_DIST = 99999

    def setUp(self):
        self.graph = load_map("mit_map.txt")

    def test_load_map_basic(self):
        self.assertTrue(isinstance(self.graph, Digraph))
        self.assertEqual(len(self.graph.nodes), 37)
        all_edges = []
        for _, edges in self.graph.edges.items():
            all_edges += edges  # edges must be dict of node -> list of edges
        all_edges = set(all_edges)
        self.assertEqual(len(all_edges), 129)

    def _print_path_description(self, start, end, total_dist, outdoor_dist):
        constraint = ""
        if outdoor_dist != Ps2Test.LARGE_DIST:
            constraint = "without walking more than {}m outdoors".format(
                outdoor_dist)
        if total_dist != Ps2Test.LARGE_DIST:
            if constraint:
                constraint += ' or {}m total'.format(total_dist)
            else:
                constraint = "without walking more than {}m total".format(
                    total_dist)

        print("------------------------")
        print("Shortest path from Building {} to {} {}".format(
            start, end, constraint))

    def _test_path(self,
                   expectedPath,
                   total_dist=LARGE_DIST,
                   outdoor_dist=LARGE_DIST):
        start, end = expectedPath[0], expectedPath[-1]
        self._print_path_description(start, end, total_dist, outdoor_dist)
        dfsPath = directed_dfs(self.graph, start, end, total_dist, outdoor_dist)
        print("Expected: ", expectedPath)
        print("DFS: ", dfsPath)
        self.assertEqual(expectedPath, dfsPath)

    def _test_impossible_path(self,
                              start,
                              end,
                              total_dist=LARGE_DIST,
                              outdoor_dist=LARGE_DIST):
        self._print_path_description(start, end, total_dist, outdoor_dist)
        with self.assertRaises(ValueError):
            directed_dfs(self.graph, start, end, total_dist, outdoor_dist)

    def test_path_one_step(self):
        self._test_path(expectedPath=['32', '56'])

    def test_path_no_outdoors(self):
        self._test_path(
            expectedPath=['32', '36', '26', '16', '56'], outdoor_dist=0)

    def test_path_multi_step(self):
        self._test_path(expectedPath=['2', '3', '7', '9'])

    def test_path_multi_step_no_outdoors(self):
        self._test_path(
            expectedPath=['2', '4', '10', '13', '9'], outdoor_dist=0)

    def test_path_multi_step2(self):
        self._test_path(expectedPath=['1', '4', '12', '32'])

    def test_path_multi_step_no_outdoors2(self):
        self._test_path(
            expectedPath=['1', '3', '10', '4', '12', '24', '34', '36', '32'],
            outdoor_dist=0)

    def test_impossible_path1(self):
        self._test_impossible_path('8', '50', outdoor_dist=0)

    def test_impossible_path2(self):
        self._test_impossible_path('10', '32', total_dist=100)

if __name__ == "__main__":
    #unittest.main()
    #digraph = load_map("test_load_map.txt")
    digraph = load_map("PS2/test_load_map.txt")
    #print(digraph.__str__())
    #print(test_load_map())
    #print(digraph.__str__())
    print(get_best_path(digraph, "4", "6", [[]], None, 0, 0))


from django.shortcuts import render
from .admin import Order
from.forms import OrderRegistrationForm

def register_orders(request):
    if request.method == "POST":
        form = OrderRegistrationForm(request.POST, request.FILES)
        if form.is_valid():
            form.save()
        else:
            print(form.errors)
    else:
        form = OrderRegistrationForm
    return render(request, "register_orders.html", {"form": form})

def order_list(request):
    orders = Order.objects.all()
    return render(request, "order_list.html", {"order": orders})
# Create your views here.

# Create your views here.


"""
TO-DO:
    - 문제 정렬 기준
"""
from collections import defaultdict
from urllib import parse
from typing import *
import fnmatch
import yaml
import os
from pytablewriter import MarkdownTableWriter

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

__tier_text = {
    "Unknown": 0,
    "Bronze V": 1,
    "Bronze IV": 2,
    "Bronze III": 3,
    "Bronze II": 4,
    "Bronze I": 5,
    "Silver V": 6,
    "Silver IV": 7,
    "Silver III": 8,
    "Silver II": 9,
    "Silver I": 10,
    "Gold V": 11,
    "Gold IV": 12,
    "Gold III": 13,
    "Gold II": 14,
    "Gold I": 15,
    "Platinum V": 16,
    "Platinum IV": 17,
    "Platinum III": 18,
    "Platinum II": 19,
    "Platinum I": 20,
    "Diamond V": 21,
    "Diamond IV": 22,
    "Diamond III": 23,
    "Diamond II": 24,
    "Diamond I": 25,
    "Ruby V": 26,
    "Ruby IV": 27,
    "Ruby III": 28,
    "Ruby II": 29,
    "Ruby I": 30,
    "Master": 31,
}

def get_list_of_files(dir_name: str):
    fileList = os.listdir(dir_name)

    result = []
    for entry in fileList:
        full_path = os.path.join(dir_name, entry)
        if os.path.isdir(full_path):
            result = result + get_list_of_files(full_path)
        else:
            result.append(full_path)

    return result

def file_read_to_end(path: str):
    f = open(path, "r", encoding='utf-8')
    content = f.read()
    f.close()
    return content

def parse_yaml_headers(mdtext: str):
    result = defaultdict(None)
    try:
        start_index = mdtext.index("---")
        if start_index != 0:
            raise ValueError(f"파싱할 수 없습니다.")

        end_index = mdtext.index("---", start_index + 1)
        mdtext = mdtext[start_index: end_index]

        result = yaml.load(mdtext, Loader=yaml.FullLoader)
    except ValueError:
        raise ValueError
    except Exception as e:
        raise e

    return result

def valid_yaml_headers(yaml_dict: dict):
    keys = ("file", "name", "src", "tags", "done", "date")
    return set(keys).issubset(yaml_dict)

def make_filedata_list(path: str):
    """
        src->ee

        eee
    """
    file_list = fnmatch.filter(get_list_of_files(path), "*.md")
    result = []

    for file in file_list:
        try:
            dir = os.path.dirname(file)
            body = file_read_to_end(file)
            headers = parse_yaml_headers(body)

            if not valid_yaml_headers(headers):
                raise ValueError("필요한 키가 포함되어있지 않음")

            # draft 처리
            if headers.get("draft"):
                continue

            item = {
                "file_name": headers.get("file"),
                "path": dir.replace('\\', '/'),
                "file": os.path.join(dir, headers.get("file")).replace('\\', '/'),
                "name": headers.get("name"),
                "src": headers.get("src"),
                "tags": sorted(headers.get("tags")),
                "done": headers.get("done"),
                "date": headers.get("date"),
                "level": headers.get("level"),
                "difficulty": headers.get("difficulty"),
            }
            result.append(item)
            print(file)

        except Exception as e:
            raise e

    return result

def make_markdown_table(hlist: list, orderby_date=False):
    """
    흠..
    """
    result = []

    # sort
    def orderKey(x): return (x["tags"], x["date"])
    if orderby_date:
        def orderKey(x): return (x["date"], x["tags"])

    hlist = sorted(hlist, key=orderKey)
    hlist = sorted(hlist, key=lambda x: x["done"], reverse=True)

    writer = MarkdownTableWriter(
        headers=["이름", "태그", "풀이", "완료", "#"],
        value_matrix=[
            [
                f"[{x['name']}]({x['src']})",
                ', '.join(x['tags']),
                f"[{x['file_name']}]({parse.quote(x['path'])})",
                f"{'✔' if x['done'] else '❌'}",
                f"{x['difficulty']}"

            ]
            for x in hlist
        ],
        add_index_column=True,
        margin=1
    )
    return writer.dumps()

debug = False

if __name__ == "__main__":
    rows = make_filedata_list("./baekjoon")
    baekjoon_table = make_markdown_table(rows)
    for tier in __tier_text.keys():
        baekjoon_table = baekjoon_table.replace(tier, f"![{tier}](./assets/{__tier_text[tier]}.svg)")

    rows = make_filedata_list("./leetcode")
    leetcode_table = make_markdown_table(rows)

    rows = make_filedata_list("./programmers")
    programmers_table = make_markdown_table(rows)

    template = file_read_to_end('./util/template.md')

    # 치환 해준다
    readme_text = template\
        .replace("__baekjoon_table__", baekjoon_table)\
        .replace("__leetcode_table__", leetcode_table)\
        .replace("__programmers_table__", programmers_table)

    # make readme.md
    if not debug:
        f = open(os.path.join(BASE_DIR, 'README.md'), "w", encoding='utf-8')
        f.write(readme_text)
        f.close()
    else:
        print(readme_text)


#! /usr/bin/python3
#-*- coding:utf-8 -*-
"""
#文件名称：dictionary_base.py
#编写人员：LHH
#项目组：系统组
#创建日期：2020/07/02
#功能描述：字典的定义与基本操作
#修改描述：
#备注：
"""

repeat_num = 45

print("****************1.字典的定义*****************")
"""
# 列表是有序的对象集合，输出是有序的
# 字典是无序的对象集合，输出是无序的
# 字典：
#     字典使用键值对(key-value)存储数据，多个键值对之间用逗号，分隔；
#     key是索引；
#     value是数据；
#     键和值之间用冒号:分隔；
#     键必须唯一；
#     键只能使用字符串、数字或元祖，
#     值可以是任何数据类型
# 字典格式：
# dict = {'key1': 'value1', 'key2': value2, 'key3': value3}
"""

stu1_dict = {'name': '张三',
        'sex': 'male',
        'gender': True,
        'age': 19,
        'height': 1.75,
        'weight': 62}
print(stu1_dict)

print("%s\n" % ('-' * repeat_num))

print("****************2.字典的基本操作*****************")

#查找
name = stu1_dict['name']
#id = stu1_dict['id']
print('名字：%s' % name)
#增加
stu1_dict['id'] = '0001'
#修改
stu1_dict['sex'] = 'female'
stu1_dict['age'] = 20
print('修改后：', stu1_dict)
#删除
stu1_dict.pop('id')
#stu1_dict.pop('name')
print('删除后：', stu1_dict)

print('字典的长度：', len(stu1_dict))

#合并字典
#注意：如果被合并的字典中包含已经存在的键值对，则会覆盖原有的键值对
tmp_dict = {'id': '0002'}
print('合并前：', stu1_dict)
stu1_dict.update(tmp_dict)
print('合并后：', stu1_dict)

#清空字典
# stu1_dict.clear()
# print(stu1_dict)

print("%s\n" % ('-' * repeat_num))

print("****************3.字典的循环遍历*****************")
#遍历字典
#变量k是每一次循环中，获取的键值对的key
stu2_dict = {'name': 'xiaoming',
        'sex': 'male',
        'gender': True,
        'id': '0001',
        'age': 18,
        'height': 1.80,
        'weight': 65}

# 遍历字典
for k in stu2_dict:
    v = stu2_dict[k]
    print('%s:\t\t\t%s' % (k, v))

key_list = []
var_list = []
# 遍历字典列表，键和值分别保存到列表中
for key, var in stu2_dict.items():
    key_list.append(key)
    var_list.append(var)
print('所有键：', key_list)
print('所有值：', var_list)

print("%s\n" % ('-' * repeat_num))

print("****************4.字典-列表组合的应用场景*****************")
#多个字典放在一个列表中，再遍历
stu_list = [stu1_dict, stu2_dict]

for stu_dict in stu_list:
    print(stu_dict)

print("%s\n" % ('-' * repeat_num))


from rest_framework import parsers, renderers, status
from rest_framework.authtoken.models import Token
from users.serializers import AuthTokenSerializer
from rest_framework.compat import coreapi, coreschema
from rest_framework.response import Response
from rest_framework.schemas import ManualSchema
from rest_framework.schemas import coreapi as coreapi_schema
from rest_framework.views import APIView
from rest_framework.permissions import AllowAny, IsAuthenticated
from users.authentication import ExpiringTokenAuthentication
from walletservice.settings import AUTH_TOKEN_EXPIRATION
from datetime import datetime, timedelta
from pytz import utc
from logging import getLogger

logger = getLogger(__name__)

class ObtainAuthToken(APIView):

    """
        ObtainAuthToken class is used as API endpoint to login and generate an authentication token
    """

    throttle_classes = ()
    permission_classes = (AllowAny,)
    parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
    renderer_classes = (renderers.JSONRenderer,)
    serializer_class = AuthTokenSerializer

    if coreapi_schema.is_enabled():
        schema = ManualSchema(
            fields=[
                coreapi.Field(
                    name="email",
                    required=True,
                    location='form',
                    schema=coreschema.String(
                        title="Username",
                        description="Valid email for authentication",
                    ),
                ),
                coreapi.Field(
                    name="password",
                    required=True,
                    location='form',
                    schema=coreschema.String(
                        title="Password",
                        description="Valid password for authentication",
                    ),
                ),
            ],
            encoding="application/json",
        )

    def get_serializer_context(self):
        return {
            'request': self.request,
            'format': self.format_kwarg,
            'view': self
        }

    def get_serializer(self, *args, **kwargs):
        kwargs['context'] = self.get_serializer_context()
        return self.serializer_class(*args, **kwargs)

    def post(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        user = serializer.validated_data['user']
        logger.info("User is requesting an auth token")

        status_code = status.HTTP_404_NOT_FOUND
        response = {
            'success': 'False',
            'status code': status_code,
        }

        if 'company' in request.stream.path:
            logger.debug("Using the path for companies")
            if not user.is_company:
                response['message'] = "Company not registered for {0}".format(user)
                logger.info("It's not a company user")
                return Response(response, status=status_code)
        elif 'client' in request.stream.path:
            logger.debug("Using the path for clients")
            if not user.is_client:
                response['message'] = "Client not registered for {0}".format(user)
                logger.info("It's not a client user")
                return Response(response, status=status_code)
        else:
            response['message'] = "Wrong URL path, method only available for client/company"
            logger.warning("It's trying to authenticate outside of companies/clients urls")
            status_code = status.HTTP_400_BAD_REQUEST
            return Response(response, status=status_code)

        token, created = Token.objects.get_or_create(user=user)
        if not created:
            logger.debug("Already exists an auth token for the user, checking if expired")
            # This is required for the time comparison
            utc_now = datetime.utcnow()
            utc_now = utc_now.replace(tzinfo=utc)

            if token.created < utc_now - timedelta(hours=AUTH_TOKEN_EXPIRATION):
                logger.debug("Token has been expired, generating a new one")
                # If token expired, delete it and create a new one
                token.delete()
                token, created = Token.objects.get_or_create(user=user)
            else:
                logger.debug("Token is not expired")
        else:
            logger.debug("A new token has been created")

        logger.info("User has been authenticated, sending the auth token")

        return Response({'token': token.key})

class DestroyAuthToken(APIView):

    """
        DestroyAuthToken class is used as API endpoint to logout and destroy the token in use
    """

    permission_classes = (IsAuthenticated,)
    authentication_classes = (ExpiringTokenAuthentication,)

    def post(self, request, *args, **kwargs):
        logger.info("User is requesting to destroy his auth token")
        token = Token.objects.get(user=request.user)
        token.delete()
        logger.info("Auth token has been destroyed")
        return Response(status=status.HTTP_200_OK)


# Course: CS261 - Data Structures
# Author: Michael Hilmes
# Assignment: 6
# Description: Undirected Graph Implementation

from collections import deque

class UndirectedGraph:
    """
    Class to implement undirected graph
    - duplicate edges not allowed
    - loops not allowed
    - no edge weights
    - vertex names are strings
    """

    def __init__(self, start_edges=None):
        """
        Store graph info as adjacency list
        DO NOT CHANGE THIS METHOD IN ANY WAY
        """
        self.adj_list = dict()

        # populate graph with initial vertices and edges (if provided)
        # before using, implement add_vertex() and add_edge() methods
        if start_edges is not None:
            for u, v in start_edges:
                self.add_edge(u, v)

    def __str__(self):
        """
        Return content of the graph in human-readable form
        DO NOT CHANGE THIS METHOD IN ANY WAY
        """
        out = [f'{v}: {self.adj_list[v]}' for v in self.adj_list]
        out = '\n  '.join(out)
        if len(out) < 70:
            out = out.replace('\n  ', ', ')
            return f'GRAPH: {{{out}}}'
        return f'GRAPH: {{\n  {out}}}'

    # ------------------------------------------------------------------ #

    def add_vertex(self, v: str) -> None:
        """
        Add new vertex to the graph
        """
        # If vertex is not already in the adj_list, add empty list to the dictionary
        if v not in self.adj_list:
            self.adj_list[v] = []

    def add_edge(self, u: str, v: str) -> None:
        """
        Add edge to the graph
        """
        # If the vertices are not equal...
        if u != v:
            # If vertex u is not in adj_list, add it
            if u not in self.adj_list:
                self.add_vertex(u)
            # If vertex v is not in adj_list, add it
            if v not in self.adj_list:
                self.add_vertex(v)
            # If vertex u does not have an edge to v, add it
            if v not in self.adj_list[u]:
                self.adj_list[u].append(v)
            # If vertex v does not have an edge to u, add it
            if u not in self.adj_list[v]:
                self.adj_list[v].append(u)

    def remove_edge(self, v: str, u: str) -> None:
        """
        Remove edge from the graph
        """
        # If both vertices v and u exist...
        if v in self.adj_list and u in self.adj_list:
            # If edge to u exists for vertex v, remove it
            if u in self.adj_list[v]:
                self.adj_list[v].remove(u)
            # If edge to v exists for vertex u, remove it
            if v in self.adj_list[u]:
                self.adj_list[u].remove(v)

    def remove_vertex(self, v: str) -> None:
        """
        Remove vertex and all connected edges
        """
        # If the vertex exists in adj_list, remove it
        self.adj_list.pop(v, None)
        # Check each remaining vertex and remove any edges connected to v
        for key in self.adj_list:
            if v in self.adj_list[key]:
                self.adj_list[key].remove(v)

    def get_vertices(self) -> []:
        """
        Return list of vertices in the graph (any order)
        """
        # Initialize empty list of vertices
        list_vertices = []
        # Iterate through adj_list and add the keys to list_vertices
        for key in self.adj_list:
            list_vertices.append(key)
        # Return the list of vertices
        return list_vertices

    def get_edges(self) -> []:
        """
        Return list of edges in the graph (any order)
        """
        # Initialize empty list to store edges
        list_edges = []
        # Iterate through the adj_list
        for key in self.adj_list:
            # For each edge associated with the given vertex key, create a sorted tuple representing the edge
            for item in self.adj_list[key]:
                edge = [key, item]
                edge.sort()
                edge_tuple = (edge[0], edge[1])
                # Add the edge to the list of edges
                list_edges.append(edge_tuple)
        # If the list of edges is non-empty, regenerate the list by calling set() to remove duplicates
        if len(list_edges) > 0:
            list_edges = list(set(list_edges))

        # Sort the list of edges and return it
        list_edges.sort()
        return list_edges

    def is_valid_path(self, path: []) -> bool:
        """
        Return true if provided path is valid, False otherwise
        """
        # Initialize a counter and get the length of the path array
        count = 1
        length = len(path)

        # If the path is empty, return True
        if length == 0:
            return True

        # If the path has one vertex and the vertex exists, return True.
        # Return False if the vertex doesn't exist
        if length == 1:
            if path[0] in self.adj_list:
                return True
            else:
                return False

        # Check that all path vertices exist. If not, return False
        for item in path:
            if item not in self.adj_list:
                return False

        # Iterate through the path checking for valid connections from one vertex
        # to the next, returning False if a connection is not valid
        while count < length:
            if path[count] in self.adj_list[path[count - 1]]:
                return_val = True
                count += 1
            else:
                return_val = False
                break
        return return_val

    def dfs(self, v_start, v_end=None) -> []:
        """
        Return list of vertices visited during DFS search
        Vertices are picked in alphabetical order
        """
        # Initialize lists for visited vertices and a stack
        visited = []
        stack = []
        # If starting vertex doesn't exist, return empty list
        if v_start not in self.adj_list:
            return visited
        # If v_end doesn't exist, set v_end to None
        if v_end is not None and v_end not in self.adj_list:
            v_end = None

        # Append the starting vertex to the stack
        stack.append(v_start)

        # Iterate while the stack is non-empty
        while len(stack) > 0:
            # Pop the top value off the stack
            pop_val = stack.pop()
            # Sort the edges in the pop_val vertex in reverse order
            temp_list = self.adj_list[pop_val]
            temp_list.sort()
            temp_list.reverse()
            # If pop_val vertex has not been visited, append it to the visited list
            if pop_val not in visited:
                visited.append(pop_val)
                # If we've reached the end specified by v_end, break out of loop
                if pop_val == v_end:
                    break
            # For each item in the sorted list of edges to vertices not yet visited, append to stack
            for item in temp_list:
                if item not in visited:
                    stack.append(item)
        # Return the list of visited vertices
        return visited

    def bfs(self, v_start, v_end=None) -> []:
        """
        Return list of vertices visited during BFS search
        Vertices are picked in alphabetical order
        """
        # Initialize empty lists for visited vertices and a queue
        visited = []
        queue = []
        # If starting vertex doesn't exist, return empty list
        if v_start not in self.adj_list:
            return visited
        # If v_end doesn't exist, set v_end to None
        if v_end is not None and v_end not in self.adj_list:
            v_end = None

        # Append the starting vertex to the queue and list of visited vertices
        queue.append(v_start)
        visited.append(v_start)
        # If the start and end vertices are the same, return the list
        if v_start == v_end:
            return visited

        # Iterate while the queue is non-empty
        while len(queue) > 0:
            # Dequeue and sort the list of edges
            dequeue = queue.pop(0)
            temp_list = self.adj_list[dequeue]
            temp_list.sort()
            # Iterate through the list of edges adding vertices to the queue and
            # visited list if they haven't been visited.
            for item in temp_list:
                if item not in visited:
                    visited.append(item)
                    queue.append(item)
                    # Break out of the loop if the end has been reached
                    if v_end in visited:
                        break
            # Break out of the loop if the end has been reached
            if v_end in visited:
                break
        # Return the list of visited vertices
        return visited

    def count_connected_components(self):
        """
        Return number of connected components in the graph
        """
        final_list = []

        if len(self.adj_list) == 0:
            return 0

        # For each vertex, run a depth first search to get a list of vertices for
        # that component, sort the list of vertices, and store in final_list as a tuple.
        for key in self.adj_list:
            dfs_list = self.dfs(key)
            dfs_list.sort()
            dfs_list = tuple(dfs_list)
            final_list.append(dfs_list)

        # Get the final count by finding the length of the list of unique, sorted dfs search results
        final_count = len(set(final_list))
        return final_count

    def has_cycle(self):
        """
        Return True if graph contains a cycle, False otherwise
        Receives: Nothing
        Returns: bool; True if cycle exists, False otherwise
        """
        # Find the length of the adjacency list, and get list of vertices
        length = len(self.adj_list)
        list_vertices = self.get_vertices()

        # Iterate through the range of the length of the adjacency list
        for i in range(0, length):
            # Initialize the first vertex, an empty list of visited vertices, and append first vertex
            vertex = list_vertices[i]
            visited = []
            visited.append(vertex)
            # Initialize empty queue
            queue = []
            # Append the first vertex and its parent to the queue (first vertex has no parent)
            queue.append((vertex, -1))

            while len(queue) > 0:
                # Dequeue the vertex, parent info from the queue
                (v, parent) = queue.pop(0)
                # Iterate through the vertices connected to the vertex
                for u in self.adj_list[v]:
                    # Append u to list of visited vertices, add u and its parent to the queue
                    if u not in visited:
                        visited.append(u)
                        queue.append((u, v))
                    # Return True if u is visited and is not a parent
                    elif u != parent:
                        return True

        return False

if __name__ == '__main__':

    print("\nPDF - method add_vertex() / add_edge example 1")
    print("----------------------------------------------")
    g = UndirectedGraph()
    print(g)

    for v in 'ABCDE':
        g.add_vertex(v)
    print(g)

    g.add_vertex('A')
    print(g)

    for u, v in ['AB', 'AC', 'BC', 'BD', 'CD', 'CE', 'DE', ('B', 'C')]:
        g.add_edge(u, v)
    print(g)

    print("\nPDF - method remove_edge() / remove_vertex example 1")
    print("----------------------------------------------------")
    g = UndirectedGraph(['AB', 'AC', 'BC', 'BD', 'CD', 'CE', 'DE'])
    g.remove_vertex('DOES NOT EXIST')
    g.remove_edge('A', 'B')
    g.remove_edge('X', 'B')
    print(g)
    g.remove_vertex('D')
    print(g)
    #
    #
    print("\nPDF - method get_vertices() / get_edges() example 1")
    print("---------------------------------------------------")
    g = UndirectedGraph()
    print(g.get_edges(), g.get_vertices(), sep='\n')
    g = UndirectedGraph(['AB', 'AC', 'BC', 'BD', 'CD', 'CE'])
    print(g.get_edges(), g.get_vertices(), sep='\n')
    #
    #
    print("\nPDF - method is_valid_path() example 1")
    print("--------------------------------------")
    g = UndirectedGraph(['AB', 'AC', 'BC', 'BD', 'CD', 'CE', 'DE'])
    test_cases = ['ABC', 'ADE', 'ECABDCBE', 'ACDECB', '', 'D', 'Z']
    for path in test_cases:
        print(list(path), g.is_valid_path(list(path)))
    #
    #
    print("\nPDF - method dfs() and bfs() example 1")
    print("--------------------------------------")
    edges = ['AE', 'AC', 'BE', 'CE', 'CD', 'CB', 'BD', 'ED', 'BH', 'QG', 'FG']
    g = UndirectedGraph(edges)
    test_cases = 'ABCDEGH'
    for case in test_cases:
        print(f'{case} DFS:{g.dfs(case)} BFS:{g.bfs(case)}')
    print('-----')
    for i in range(1, len(test_cases)):
        v1, v2 = test_cases[i], test_cases[-1 - i]
        print(f'{v1}-{v2} DFS:{g.dfs(v1, v2)} BFS:{g.bfs(v1, v2)}')
    #
    #
    print("\nPDF - method count_connected_components() example 1")
    print("---------------------------------------------------")
    edges = ['AE', 'AC', 'BE', 'CE', 'CD', 'CB', 'BD', 'ED', 'BH', 'QG', 'FG']
    g = UndirectedGraph(edges)
    test_cases = (
        'add QH', 'remove FG', 'remove GQ', 'remove HQ',
        'remove AE', 'remove CA', 'remove EB', 'remove CE', 'remove DE',
        'remove BC', 'add EA', 'add EF', 'add GQ', 'add AC', 'add DQ',
        'add EG', 'add QH', 'remove CD', 'remove BD', 'remove QG')
    for case in test_cases:
        command, edge = case.split()
        u, v = edge
        g.add_edge(u, v) if command == 'add' else g.remove_edge(u, v)
        print(g.count_connected_components(), end=' ')
    print()
    #
    #
    print("\nPDF - method has_cycle() example 1")
    print("----------------------------------")
    edges = ['AE', 'AC', 'BE', 'CE', 'CD', 'CB', 'BD', 'ED', 'BH', 'QG', 'FG']
    g = UndirectedGraph(edges)
    test_cases = (
        'add QH', 'remove FG', 'remove GQ', 'remove HQ',
        'remove AE', 'remove CA', 'remove EB', 'remove CE', 'remove DE',
        'remove BC', 'add EA', 'add EF', 'add GQ', 'add AC', 'add DQ',
        'add EG', 'add QH', 'remove CD', 'remove BD', 'remove QG',
        'add FG', 'remove GE')
    for case in test_cases:
        command, edge = case.split()
        u, v = edge
        g.add_edge(u, v) if command == 'add' else g.remove_edge(u, v)
        print('{:<10}'.format(case), g.has_cycle())


import qgis, qgis.utils, os, datetime, PIL, platform
if platform.system() == 'Windows':
    from PIL import ImageGrab
from PIL.ImageQt import ImageQt

from qgis.PyQt import uic, QtCore, QtGui
from qgis.PyQt.QtWidgets import (QWidget,QMainWindow, QSpinBox, QAction, QDialog, QFrame)

from ...lamia_abstractformtool import AbstractLamiaFormTool
from .lamiabase_photoviewer import PhotoViewer

class BaseCroquisTool(AbstractLamiaFormTool):

    PREPROTOOLNAME = 'Croquis'
    DBASETABLENAME = 'Photo'
    LOADFIRST = False

    tooltreewidgetCAT = 'Ressources'
    tooltreewidgetSUBCAT = 'Croquis'
    tooltreewidgetICONPATH = os.path.join(os.path.dirname(__file__), 'lamiabase_croquis_tool_icon.png')

    tempparentjoin = {}
    linkdict = {'colparent': 'id_objet',
                'colthistable': 'id_ressource',
                    'tctable': 'Tcobjetressource',
                    'tctablecolparent':'lid_objet',
                    'tctablecolthistable':'lid_ressource'}
    for tablename in ['Observation', 'Noeud', 'Infralineaire', 'Equipement']:
        tempparentjoin[tablename] = linkdict
    PARENTJOIN = tempparentjoin

    TABLEFILTERFIELD = {'typephoto': 'CRO' }
    GEOMETRYSKIP = True

    def __init__(self, **kwargs):
        super(BaseCroquisTool, self).__init__(**kwargs)
    """
    def initTool(self):
        # ****************************************************************************************
        # Main spec
        self.CAT = 'Ressources'
        self.NAME = 'Croquis'
        self.dbasetablename = 'Photo'
        self.visualmode = [ 1, 2]
        # self.PointENABLED = True
        # self.LineENABLED = True
        # self.PolygonEnabled = True
        # self.magicfunctionENABLED = True
        self.linkagespec = {'Tcobjetressource' : {'tabletc' : 'Tcobjetressource',
                                              'idsource' : 'id_ressource',
                                            'idtcsource' : 'lid_ressource',
                                           'iddest' : 'id_objet',
                                           'idtcdest' : 'lid_objet',
                                           'desttable' : ['Profil','Infralineaire','Observation','Equipement','Noeud']} }
        # self.pickTable = None
        self.iconpath = os.path.join(os.path.dirname(__file__), 'lamiabase_croquis_tool_icon.png')

        # ****************************************************************************************
        #properties ui
        pass
    """

    def initMainToolWidget(self):
        # ****************************************************************************************
        # userui
        self.toolwidgetmain = UserUI()
        self.formtoolwidgetconfdictmain = {'Photo' : {'linkfield' : 'id_photo',
                                            'widgets' : {}},
                                            'Objet' : {'linkfield' : 'id_objet',
                                                        'widgets' : {}},
                                            'Ressource' : {'linkfield' : 'id_ressource',
                                                        'widgets' : {}}}

        # self.groupBox_geom.setParent(None)
        # self.frame_editing.setVisible(False)
        self.toolwidgetmain.stackedWidget.setCurrentIndex(1)

        self.toolwidgetmain.pushButton_open.clicked.connect(self.openPhoto)
        self.toolwidgetmain.pushButton_edit.clicked.connect(self.editPhoto)
        self.toolwidgetmain.pushButton_getfromclipboard.clicked.connect(self.pasteImage)
        self.editorwindow = ScribbleMainWindow(parentwdg=self)
        self.photowdg = PhotoViewer()
        self.toolwidgetmain.frame_cr.layout().addWidget(self.photowdg)

    """
    def postOnActivation(self):
        pass

    def postOnDesactivation(self):
        pass

    def postloadIds(self,sqlin):
        sqlin += " AND typephoto = 'CRO'"
        return sqlin
    """
    #def postInitFeatureProperties(self, feat):
    def postSelectFeature(self):

        if self.currentFeaturePK is None:   #first time
            datecreation = str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
            self.formutils.applyResultDict({'Ressource':{'datetimeressource' : datecreation}})
            self.editorwindow.reinitSize()
            self.editorwindow.clear()
            self.photowdg.clear()

        else:
            sql = "SELECT file FROM photo_qgis  WHERE pk_photo = " + str(self.currentFeaturePK)
            file = self.dbase.query(sql)[0][0]
            if file is not None and file != '' and os.path.isfile(self.dbase.completePathOfFile(file)) :
                self.editorwindow.openImage(self.dbase.completePathOfFile(file))
                self.formutils.showImageinLabelWidget(self.photowdg, self.dbase.completePathOfFile(file))
            else:
                self.editorwindow.clear()
                self.photowdg.clear()

    def pasteImage(self):
        if platform.system() == 'Windows':
            pilimage = PIL.ImageGrab.grabclipboard()
            if pilimage is not None:
                im = ImageQt(pilimage )
                # self.editorwindow.scribbleArea
                #self.image = loadedImage
                #self.modified = False
                #self.update()
                self.editorwindow.setImage(im)
                #self.editorwindow.scribbleArea.image = im
                #self.editorwindow.scribbleArea.update()
                self.photowdg.clear()
                self.photowdg.setPixmap(im)

                self.editPhoto()

    def editPhoto(self):
        if qgis.utils.iface is not None :
            self.editorwindow.show()
        else:
            self.editorwindow.setWindowModality(QtCore.Qt.ApplicationModal)
            self.editorwindow.show()

    def openPhoto(self):
        if False:
            if os.path.isfile(self.dbase.completePathOfFile(self.currentFeature['File'] )):
                filepath = self.dbase.completePathOfFile(self.currentFeature['File'])
                os.startfile(filepath)

        if True:
            #sql = "SELECT file FROM Ressource  WHERE id_ressource = " + str(self.currentFeature['id_ressource']) + ";"
            sql = "SELECT file FROM photo_qgis  WHERE pk_photo = " + str(self.currentFeaturePK)
            query = self.dbase.query(sql)
            #result = [row[0] for row in query]
            resultfile = query[0][0]
            if os.path.isfile(self.dbase.completePathOfFile(resultfile)):
                filepath = self.dbase.completePathOfFile(resultfile)
                os.startfile(filepath)

    """
    def createParentFeature(self):
        pkobjet = self.dbase.createNewObjet()

        if False:
            # lastrevision = self.dbase.maxrevision
            datetimecreation =  str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
            lastobjetid = self.dbase.getLastId('Objet') + 1
            sql = "INSERT INTO Objet (id_objet, lpk_revision_begin, datetimecreation ) "
            sql += "VALUES(" + str(lastobjetid ) + "," + str(self.dbase.maxrevision) +  ",'" + datetimecreation + "');"
            query = self.dbase.query(sql)
            self.dbase.commit()
            pkobjet = self.dbase.getLastRowId('Objet')

        lastressourceid = self.dbase.getLastId('Ressource') + 1
        sql = "INSERT INTO Ressource (id_ressource, lpk_objet) "
        sql += "VALUES(" + str(lastressourceid) +   "," + str(pkobjet) + ");"
        query = self.dbase.query(sql)
        self.dbase.commit()
        pkres = self.dbase.getLastRowId('Ressource')

        pkphoto = self.currentFeaturePK
        lastidphoto = self.dbase.getLastId('Photo') + 1
        datecreation = datetime.datetime.now().strftime("%Y-%m-%d")
        datetimecreation = str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

        fileimage = os.path.join('.', self.dbasetablename, ''.join(datecreation.split('-')),
                                 str(lastidphoto) + '_croquis.png')
        if not os.path.exists(os.path.dirname(self.dbase.completePathOfFile(fileimage))):
            os.makedirs(os.path.dirname(self.dbase.completePathOfFile(fileimage)))
        self.editorwindow.saveImage(self.dbase.completePathOfFile(fileimage))

        sql = "UPDATE Photo SET id_photo = " + str(lastidphoto)  + ","
        sql += "lpk_ressource = " + str(pkres)
        sql += ", typephoto = 'CRO' "
        sql += " WHERE pk_photo = " + str(pkphoto) + ";"
        query = self.dbase.query(sql)
        self.dbase.commit()

        sql = "UPDATE Ressource SET  file = '" + fileimage + "', datetimeressource = '" + datetimecreation + "'"
        sql += " WHERE pk_ressource = " + str(pkres) + ";"
        query = self.dbase.query(sql)
        self.dbase.commit()

        if self.parentWidget is not None and self.parentWidget.currentFeature is not None:
            # self.linkagespec = {'Tcobjetressource'
            if 'Tcobjetressource' in self.linkagespec.keys():
                #get parent id_objet
                sql = " SELECT id_objet FROM " + self.parentWidget.dbasetablename.lower() + "_qgis"
                sql += " WHERE pk_" + self.parentWidget.dbasetablename.lower() + " = " + str(self.parentWidget.currentFeaturePK)
                currentparentlinkfield = self.dbase.query(sql)[0][0]

                #currentparentlinkfield = self.parentWidget.currentFeature['id_objet']
                sql = "INSERT INTO Tcobjetressource(lpk_revision_begin, lid_objet, lid_ressource) "
                sql += " VALUES(" + str(self.dbase.maxrevision) + "," + str(currentparentlinkfield) + ',' + str(lastressourceid) + ")"
                query = self.dbase.query(sql)
                self.dbase.commit()

        if False:
            #lastrevision = self.dbase.getLastPk('Revision')
            lastrevision = self.dbase.maxrevision
            datecreation = QtCore.QDate.fromString(str(datetime.date.today()), 'yyyy-MM-dd').toString('yyyy-MM-dd')
            lastobjetid = self.dbase.getLastId('Objet') + 1
            sql = "INSERT INTO Objet (id_objet, id_revisionbegin, datecreation ) "
            sql += "VALUES(" + str(lastobjetid ) + "," + str(lastrevision) +  ",'" + datecreation + "');"
            query = self.dbase.query(sql)
            self.dbase.commit()
            #idobjet = self.dbase.getLastRowId('Objet')

            lastressourceid = self.dbase.getLastId('Ressource') + 1
            sql = "INSERT INTO Ressource (id_ressource, id_revisionbegin, id_objet) "
            sql += "VALUES(" + str(lastressourceid) + "," + str(lastrevision) +  "," + str(lastobjetid) + ");"
            query = self.dbase.query(sql)
            self.dbase.commit()
            lastressourcepk = self.dbase.getLastRowId('Ressource')

            pkcroquis = self.currentFeature.id()
            lastidcroquis = self.dbase.getLastId('Photo') + 1

            fileimage = os.path.join('.', self.dbasetablename, ''.join(datecreation.split('-')),
                                     str(lastidcroquis) + '_croquis.png')
            if not os.path.exists(os.path.dirname(self.dbase.completePathOfFile(fileimage))):
                os.makedirs(os.path.dirname(self.dbase.completePathOfFile(fileimage)))
            self.editorwindow.saveImage(self.dbase.completePathOfFile(fileimage))

            sql = "UPDATE Photo SET id_objet = " + str(lastobjetid)  + ","
            sql += "id_ressource = " + str(lastressourceid)   + ","
            sql += "id_photo = " + str(lastidcroquis)  + ","
            sql += "id_revisionbegin = " + str(lastrevision) + ","
            sql += "typephoto = 'CRO' "
            sql += " WHERE pk_photo = " + str(pkcroquis) + ";"
            query = self.dbase.query(sql)
            self.dbase.commit()

            sql = "UPDATE Ressource SET  file = '" + fileimage + "', dateressource = '" + datecreation + "'"
            sql += " WHERE pk_ressource = " + str( lastressourcepk) + ";"
            query = self.dbase.query(sql)
            self.dbase.commit()

            if self.parentWidget is not None and self.parentWidget.currentFeature is not None:
                currentparentlinkfield = self.parentWidget.currentFeature['id_objet']
                sql = "INSERT INTO Tcobjetressource(id_tcobjet, id_tcressource,id_revisionbegin) "
                sql += " VALUES(" + str(currentparentlinkfield) + ", " + str(lastressourceid) + "," + str(lastrevision) + ");"
                query = self.dbase.query(sql)
                self.dbase.commit()
    """

    def postSaveFeature(self, savedfeaturepk=None):

        if self.currentFeaturePK is None:   #first creation
            idphoto, pkres = self.dbase.getValuesFromPk(self.DBASETABLENAME.lower() + '_qgis',
                                                 ['id_' + self.DBASETABLENAME.lower(),'pk_ressource'],
                                                 savedfeaturepk)
            datecreation = datetime.datetime.now().strftime("%Y-%m-%d")
            datetimecreation = str(datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))

            sql = "UPDATE Photo SET  typephoto = 'CRO' WHERE pk_photo = {}".format(str(savedfeaturepk))
            query = self.dbase.query(sql)

            fileimage = os.path.join('.', self.DBASETABLENAME, ''.join(datecreation.split('-')),
                                    str(idphoto) + '_croquis.png')
            if not os.path.exists(os.path.dirname(self.dbase.completePathOfFile(fileimage))):
                os.makedirs(os.path.dirname(self.dbase.completePathOfFile(fileimage)))
            #self.editorwindow.saveImage(self.dbase.completePathOfFile(fileimage))

            sql = "UPDATE Ressource SET  file = '" + fileimage + "', datetimeressource = '" + datetimecreation + "'"
            sql += " WHERE pk_ressource = " + str(pkres)
            query = self.dbase.query(sql)

        else:
            """
            sql = "SELECT file FROM Photo_qgis WHERE pk_photo = " + str(self.currentFeaturePK)
            query = self.dbase.query(sql)
            fileimage = [row[0] for row in query][0]
            """
            fileimage = self.dbase.getValuesFromPk(self.DBASETABLENAME + '_qgis',
                                                 'file',
                                                 self.currentFeaturePK )
        self.editorwindow.saveImage(self.dbase.completePathOfFile(fileimage))

    """
    def deleteParentFeature(self):
        sql = "SELECT pk_objet, pk_ressource, id_ressource FROM Photo_qgis WHERE pk_photo = " + str(self.currentFeaturePK)
        pkobjet, pkressource, idressource = self.dbase.query(sql)[0]
        #idobjet = self.currentFeature['id_objet']
        #idressource = self.currentFeature['id_ressource']

        sql = "DELETE FROM Objet WHERE pk_objet = " + str(pkobjet)
        query = self.dbase.query(sql)
        self.dbase.commit()

        sql = "DELETE FROM Ressource WHERE pk_ressource = " + str(pkressource)
        query = self.dbase.query(sql)
        self.dbase.commit()

        sql = "DELETE FROM Tcobjetressource WHERE id_tcressource = " + str(idressource)
        sql += " AND lpk_revision_begin <= " + str(self.dbase.maxrevision)
        sql += " AND lpk_revision_end IS  NULL "

        return True
    """

class UserUI(QWidget):
    def __init__(self, parent=None):
        super(UserUI, self).__init__(parent=parent)
        uipath = os.path.join(os.path.dirname(__file__), 'lamiabase_photo_tool_ui.ui')
        uic.loadUi(uipath, self)

class ScribbleMainWindow(QMainWindow):
    def __init__(self, parentwdg=None, parent=None):
        super(ScribbleMainWindow, self).__init__(parent=parent)
        uipath = os.path.join(os.path.dirname(__file__), 'lamiabase_croquis_tool_drawingwidg_ui.ui')
        uic.loadUi(uipath, self)
        self.parentwdg = parentwdg
        self.scribbleArea = ScribbleArea(parent=self)
        self.scribbleArea.clearImage()
        self.scribbleArea.mainWindow = self  # maybe not using this?

        self.scrollArea.setWidget(self.scribbleArea)

        self.toolbar = self.addToolBar('Color')
        self.colorwdg = qgis.gui.QgsColorButton()
        self.colorwdg.setColor(QtCore.Qt.black)
        self.toolbar.addWidget(self.colorwdg)
        self.spinb = QSpinBox()
        self.spinb.setValue(3)
        self.toolbar.addWidget(self.spinb)

        self.spinb.valueChanged.connect(self.scribbleArea.setPenWidth)
        self.colorwdg.colorChanged.connect(self.scribbleArea.setPenColor)

        self.clearAction = QAction('Clear', self)
        self.clearAction.triggered.connect(self.scribbleArea.clearImage)
        self.toolbar.addAction(self.clearAction)

        self.reinitAction = QAction('reinit', self)
        self.reinitAction.triggered.connect(self.scribbleArea.reinit)
        self.toolbar.addAction(self.reinitAction)

    def reinitSize(self):
        self.scribbleArea.reinitSize()

    def saveImage(self,file):
        self.scribbleArea.saveImage(file,"png")

    def clear(self):
        self.scribbleArea.clearImage()

    def openImage(self,file):
        self.scribbleArea.openImage(file)

    def setImage(self, qimage):
        self.scribbleArea.setImage(qimage)

    def closeEvent(self, event):
        self.parentwdg.photowdg.clear()
        self.parentwdg.photowdg.setPixmap(self.scribbleArea.image)
        event.accept()

class ScribbleArea(QWidget):
    """
      this scales the image but it's not good, too many refreshes really mess it up!!!
    """
    def __init__(self, larg=500,haut=500, parent=None):
        super(ScribbleArea, self).__init__(parent)

        #self.setAttribute(QtCore.Qt.WA_StaticContents)
        self.setFixedSize(larg, haut)
        self.modified = False
        self.scribbling = False
        self.myPenWidth = 3
        self.myPenColor = QtCore.Qt.black
        imageSize = QtCore.QSize(larg, haut)
#       self.image = QtGui.QImage()
        self.image = QtGui.QImage(imageSize, QtGui.QImage.Format_RGB32)
        self.lastPoint = QtCore.QPoint()

        self.currentfilename=None

    def openImage(self, fileName=None):
        loadedImage = QtGui.QImage()
        if not loadedImage.load(fileName):
            return False
        self.currentfilename = fileName
        return self.setImage(loadedImage)

    def reinit(self):
        if self.currentfilename is not None:
            self.openImage(self.currentfilename)

    def reinitSize(self):
        self.setFixedSize(500, 500)
        self.mainWindow.resize(500, 500)
        imageSize = QtCore.QSize(500, 500)
        self.image = QtGui.QImage(imageSize, QtGui.QImage.Format_RGB32)

    def setImage(self, loadedImage):
        w = loadedImage.width()
        h = loadedImage.height()
        self.setFixedSize(w, h)
        self.mainWindow.resize(w, h)

        #       newSize = loadedImage.size().expandedTo(self.size())
        #       self.resizeImage(loadedImage, newSize)
        self.image = loadedImage
        self.modified = False
        self.update()
        return True

    def saveImage(self, fileName, fileFormat):
        if self.image.save(fileName, fileFormat):
            self.modified = False
            return True
        else:
            return False

    def setPenColor(self, newColor):
        self.myPenColor = newColor

    def setPenWidth(self, newWidth):
        self.myPenWidth = newWidth

    def clearImage(self):
        #print('celar')
        self.image.fill(QtGui.qRgb(255, 255, 255))
        self.modified = True
        self.update()

    def mousePressEvent(self, event):
#       print "self.image.width() = %d" % self.image.width()
#       print "self.image.height() = %d" % self.image.height()
#       print "self.image.size() = %s" % self.image.size()
#       print "self.size() = %s" % self.size()
#       print "event.pos() = %s" % event.pos()
        if event.button() == QtCore.Qt.LeftButton:
            self.lastPoint = event.pos()
            self.scribbling = True

    def mouseMoveEvent(self, event):
        if (event.buttons() & QtCore.Qt.LeftButton) and self.scribbling:
            self.drawLineTo(event.pos())

    def mouseReleaseEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton and self.scribbling:
            self.drawLineTo(event.pos())
            self.scribbling = False

    def paintEvent(self, event):
        painter = QtGui.QPainter(self)
        painter.drawImage(event.rect(), self.image,event.rect())

    if False:
        def resizeEvent(self, event):
            #       print "resize event"
            #       print "event = %s" % event
            #       print "event.oldSize() = %s" % event.oldSize()
            #       print "event.size() = %s" % event.size()

            self.resizeImage(self.image, event.size())

            #       if self.width() > self.image.width() or self.height() > self.image.height():
            #           newWidth = max(self.width() + 128, self.image.width())
            #           newHeight = max(self.height() + 128, self.image.height())
            #           print "newWidth = %d, newHeight = %d" % (newWidth, newHeight)
            #           self.resizeImage(self.image, QtCore.QSize(newWidth, newHeight))
            #           self.update()

            super(ScribbleArea, self).resizeEvent(event)

    def drawLineTo(self, endPoint):
        painter = QtGui.QPainter(self.image)
        painter.setPen(QtGui.QPen(self.myPenColor, self.myPenWidth,
            QtCore.Qt.SolidLine, QtCore.Qt.RoundCap, QtCore.Qt.RoundJoin))
        painter.drawLine(self.lastPoint, endPoint)
        self.modified = True

        # rad = self.myPenWidth / 2 + 2
        # self.update(QtCore.QRect(self.lastPoint, endPoint).normalized().adjusted(-rad, -rad, +rad, +rad))
        self.update()
        self.lastPoint = QtCore.QPoint(endPoint)

    if False:
        def resizeImage(self, image, newSize):
            if image.size() == newSize:
                return

            #       print "image.size() = %s" % repr(image.size())
            #       print "newSize = %s" % newSize

            # this resizes the canvas without resampling the image
            newImage = QtGui.QImage(newSize, QtGui.QImage.Format_RGB32)
            newImage.fill(QtGui.qRgb(255, 255, 255))
            painter = QtGui.QPainter(newImage)
            painter.drawImage(QtCore.QPoint(0, 0), image)

            ##  this resampled the image but it gets messed up with so many events...
            ##      painter.setRenderHint(QtGui.QPainter.SmoothPixmapTransform, True)
            ##      painter.setRenderHint(QtGui.QPainter.HighQualityAntialiasing, True)
            #
            #       newImage = QtGui.QImage(newSize, QtGui.QImage.Format_RGB32)
            #       newImage.fill(QtGui.qRgb(255, 255, 255))
            #       painter = QtGui.QPainter(newImage)
            #       srcRect = QtCore.QRect(QtCore.QPoint(0,0), image.size())
            #       dstRect = QtCore.QRect(QtCore.QPoint(0,0), newSize)
            ##      print "srcRect = %s" % srcRect
            ##      print "dstRect = %s" % dstRect
            #       painter.drawImage(dstRect, image, srcRect)

            self.image = newImage

    def print_(self):
        printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution)

        printDialog = QtGui.QPrintDialog(printer, self)
        if printDialog.exec_() == QDialog.Accepted:
            painter = QtGui.QPainter(printer)
            rect = painter.viewport()
            size = self.image.size()
            size.scale(rect.size(), QtCore.Qt.KeepAspectRatio)
            painter.setViewport(rect.x(), rect.y(), size.width(), size.height())
            painter.setWindow(self.image.rect())
            painter.drawImage(0, 0, self.image)
            painter.end()

    def isModified(self):
        return self.modified

    def penColor(self):
        return self.myPenColor

    def penWidth(self):
        return self.myPenWidth


"""
WAP to input a string, capitalize every alternate character in a string
E.g: original string : computer science
 new string after alternate character in upper : cOmPuTeR ScIeNcE
"""
char_list = list(input("Enter a string: "))

for i in range(1, len(char_list), 2):
    char_list[i - 1] = char_list[i - 1].lower()
    char_list[i] = char_list[i].upper()

print("Alternate Case: ")
print("".join(char_list))

"""
OUTPUT:
Enter a string: computer science
Alternate Case:
cOmPuTeR ScIeNcE
"""


# zadatak 1.6 Ako je prvi dan u mesecu ponedeljak napisati funkciju radni_dan(dan) koja
# kao argument dobija dan u mesecu i vraća tačno ako je dan radni dan. Napisati program koji
# testira ovu funkciju, korisnik sa standardnog ulaza u petlji unosi deset dana i dobija o poruku o
# tome da li su uneti dani radni ili ne.

def ucitaj_dan(x):
    if x % 7 == 0 or x % 7 == 6:
        return False
    else:
        return True

for i in range(10):
    if ucitaj_dan(int(input())):
        print("radan")
    else:
        print("neradan")


import random
import collections
import torch
from PIL import Image
from torchvision import transforms

class strLabelConverter(object):
    def __init__(self, alphabet, ignore_case=False):
        self._ignore_case = ignore_case
        if self._ignore_case:
            alphabet = alphabet.lower()

        self.alphabet = alphabet
        self.alphabet.append(ord('_'))

        self.dict = {}
        for i, char in enumerate(alphabet):
            self.dict[char] = i + 1

    def encode(self, text):
        try:
            if isinstance(text, str):
                text_ = []
                for char in text:
                    if self._ignore_case:
                        char.lower()
                    char = ord(char)
                    if char in self.dict.keys():
                        text_.append(self.dict[char])
                    else:
                        text_.append(0)
                text = text_
                length = [len(text)]
            elif isinstance(text, collections.Iterable):
                length = [len(s) for s in text]
                text = ''.join(text)
                text, _ = self.encode(text)
        except KeyError as e:
            print(e)
            for ch in text:
                if ord(ch) not in self.dict.keys():
                    print('Not Found Char: {} - {}'.format(ch, ord(ch)))
        return (torch.IntTensor(text), torch.IntTensor(length))

    def decode(self, t, length, raw=False):

        if length.numel() == 1:
            length = length[0]
            assert t.numel() == length
            if raw:
                return ''.join([chr(self.alphabet[i - 1]) for i in t])
            else:
                char_list = []
                for i in range(length):
                    if t[i] != 0 and (not (i > 0 and t[i - 1] == t[i])):
                        char_list.append(chr(self.alphabet[t[i] - 1]))
                return ''.join(char_list)
        else:
            assert t.numel() == length.sum()
            text = []
            index = 0
            _ = length.numel()
            for i in range(length.numel()):
                l = length[i]
                text.append(
                    self.decode(t[index:index + l], torch.IntTensor([l]), raw=raw)
                )
                index += l
            return text

class resizeNormalize(object):
    def __init__(self, size, interpolation=Image.LANCZOS, is_test=False):
        self.size = size
        self.interpolation = interpolation
        self.toTensor = transforms.ToTensor()
        self.is_test = is_test

    def __call__(self, img):
        w, h = self.size
        w0 = img.size[0]
        h0 = img.size[1]
        if w <= (w0 / h0 * h):
            img = img.resize(self.size, self.interpolation)
            img = self.toTensor(img)
            img.sub_(0.5).div_(0.5)
        else:
            w_real = int(w0 / h0 * h)
            img = img.resize((w_real, h), self.interpolation)
            img = self.toTensor(img)
            img.sub_(0.5).div_(0.5)
            start = random.randint(0, w - w_real - 1)
            if self.is_test:
                start = 5
                w += 10
            tmp = torch.zeros([img.shape[0], h, w]) + 0.5
            tmp[:, :, start:start + w_real] = img
            img = tmp
        return img


import base64
import codecs
from .globalvar import config
import os.path

def resource(filename, binary=False, encoding="utf-8", b64=False, useConfig=True):
    """Return file cotnent

    :param filename: file path
    :type filename:str
    :param binary: open file mode, defaults to "rb" if is bianry else "r"
    :type mode: Boolean
    :param encoding:  content encoding, defaults to "utf-8"
    :type encoding: str, optional
    :param b64: if True will return base64 encoding text, defaults to False
    :type b64: bool, optional
    :returns: the file content
    :rtype: str|bytes
    """

    if useConfig:
        path = config.get("resource")
        if path:
            filename = os.path.join(path, filename)

    mode = "rb" if binary else "r"
    if binary:
        encoding = None
    with codecs.open(filename, mode, encoding) as f:
        content = f.read()
        if b64:
            content = base64.b64encode(content)
        return content

class lazy_attr(object):
    """Lazy attr decorator"""

    def __init__(self, wrapped):
        self.wrapped = wrapped
        try:
            self.__doc__ = wrapped.__doc__
        except:  # pragma: no cover
            pass

    def __get__(self, inst, objtype=None):
        if inst is None:
            return self
        val = self.wrapped(inst)
        setattr(inst, self.wrapped.__name__, val)
        return val


import asyncio
import multidex
import ccxt.async_support as ccxt
import argparse

def get_exchange(exchange):
    if exchange == 'binance':
        return ccxt.binance({'enableRateLimit': True})
    elif exchange == 'bitfinex':
        return ccxt.bitfinex({'enableRateLimit': True})
    elif exchange == 'kucoin':
        return ccxt.kucoin({'enableRateLimit': True})
    elif exchange == 'bybit':
        return ccxt.bybit({'enableRateLimit': True})
    elif exchange == 'ftx':
        return ccxt.ftx({'enableRateLimit': True})
    else:
        raise Exception("Exchange not supported")

def get_dex(dex):
    if dex == 'stellaswap':
        return multidex.Stellaswap()
    elif dex == 'uniswap':
        return multidex.Uniswap()
    elif dex == 'spookyswap':
        return multidex.Spookyswap()
    elif dex == 'pancakeswap':
        return multidex.Pancakeswap()
    elif dex == 'beamswap':
        return multidex.Beamswap()
    else:
        raise Exception("Dex not supported")

def get_arguments():
    parser = argparse.ArgumentParser()
    parser.add_argument("-e", "--exchange", help = "Select exchange")
    parser.add_argument("-p", "--pair", help = "Select pair")
    parser.add_argument("-d", "--dex", help = "Select dex")
    parser.add_argument("-t", "--token", help = "Select token")
    return parser.parse_args()

async def main():

    args = get_arguments()
    exchange = get_exchange(args.exchange)
    ticker = await exchange.fetch_ticker(args.pair)
    print(args.exchange, " ask: ", ticker["ask"])
    print(args.exchange, " bid: ", ticker["bid"])
    print(args.exchange, " askVolume: ", ticker["askVolume"])
    print(args.exchange, " bidVolume: ", ticker["bidVolume"])
    print(args.exchange, " last: ", ticker["last"])
    await exchange.close()

    dex = get_dex(args.dex)
    print(args.dex, " reserve_ratio: ", dex.reserve_ratio(args.token))
    print(args.dex, " price: ", dex.price(args.token))
    print(args.dex, " liquidity: ", dex.liquidity(args.token))

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())


# -*- coding: utf-8 -*-
"""
Created on Fri Apr 19 07:23:58 2019

@author: roma
"""

# Importing the libraries
import os
import numpy as np
import pandas as pd
from sklearn.model_selection import StratifiedKFold
from sklearn.ensemble import RandomForestClassifier, ExtraTreesClassifier
from sklearn.preprocessing import LabelEncoder

# Importing the dataset
train_X1 = pd.read_csv('X_train.csv')
test_X1 = pd.read_csv('X_test.csv' )
cols=["row_id","series_id","measurement_number"]

train_X=train_X1.drop(cols,axis=1).iloc[:,3:].values.reshape(-1,128,10)

test_X=test_X1.drop(cols,axis=1).values.reshape(-1,128,10)

df_train_y =pd.read_csv('y_train.csv')

train_y=df_train_y['surface']

surface_names = df_train_y['surface'].unique()
num_surfaces = len(surface_names)
surface_to_numeric = dict(zip(surface_names, range(num_surfaces)))

#titanic train part
cols=["row_id","measurement_number"]
train_y = df_train_y['surface'].replace(surface_to_numeric).values
train_X=train_X1.drop(cols,axis=1)

test_X=test_X1.drop(cols,axis=1)

def FE(data):
    df = pd.DataFrame()
    #craetion of another variables(total angular velocity) using relationship
    data['total_anglr_vel'] = (data['angular_velocity_X']**2 + data['angular_velocity_Y']**2 +
                             data['angular_velocity_Z']**2)** 0.5
    data['total_linr_acc'] = (data['linear_acceleration_X']**2 + data['linear_acceleration_Y']**2 +
                             data['linear_acceleration_Z']**2)**0.5
    data['total_xyzW'] = (abs(data['orientation_X']) + abs(data['orientation_Y']) +
                              abs(data['orientation_Z'])+abs(data['orientation_W']))/4

    data['acc_vs_vel'] = data['total_linr_acc'] / data['total_anglr_vel']
    data['acc_vs_vel'] = data['total_linr_acc'] * data['total_anglr_vel']
    data['quaternion1'] = 2*np.arccos(data['orientation_W'])

    #using python basics function and some gymnastics(obtained on internet)
    for col in data.columns:
        if col in ['row_id','series_id','measurement_number', 'orientation_X', 'orientation_Y', 'orientation_Z', 'orientation_W']:
            continue
        df[col + '_mean'] = data.groupby(['series_id'])[col].mean()
        df[col + '_median'] = data.groupby(['series_id'])[col].median()
        df[col + '_max'] = data.groupby(['series_id'])[col].max()
        df[col + '_min'] = data.groupby(['series_id'])[col].min()
        df[col + '_std'] = data.groupby(['series_id'])[col].std()
        df[col + '_range'] = df[col + '_max'] - df[col + '_min']
        df[col + '_maxtoMin'] = df[col + '_max']/ df[col + '_min']
        df[col + '_mean_abs_chg'] = data.groupby(['series_id'])[col].apply(lambda x: np.mean(np.abs(np.diff(x))))
        df[col + '_abs_max'] = data.groupby(['series_id'])[col].apply(lambda x: np.max(np.abs(x)))
        df[col + '_abs_min'] = data.groupby(['series_id'])[col].apply(lambda x: np.min(np.abs(x)))
        df[col + '_abs_avg'] = (df[col + '_abs_min'] + df[col + '_abs_max'])/2
    return df
new_train = FE(train_X)

test = FE(test_X)
""" is an cross validation technique (randomly dividing datasets into k groups)"""

def k_folds(clf, X, t, X_test, k):
    folds = StratifiedKFold(n_splits = k, shuffle=True, random_state=13)
    y_test = np.zeros((X_test.shape[0], 9))
    y_oof = np.zeros((X.shape[0]))
    score = 0
    for i, (train_idx, val_idx) in  enumerate(folds.split(X, t)):
        clf.fit(X[train_idx], t[train_idx])
        y_oof[val_idx] = clf.predict(X[val_idx])
        y_test += clf.predict_proba(X_test) / folds.n_splits
        score += clf.score(X[val_idx], t[val_idx])
    return y_oof, y_test

label1 = pd.read_csv("y_train.csv")
surface_names = label1['surface'].unique()
num_surfaces = len(surface_names)
surface_to_numeric = dict(zip(surface_names, range(num_surfaces)))

label = label1['surface'].replace(surface_to_numeric).values
ntv=new_train.values
tst=test.values

#Feature Scaling
le = LabelEncoder()

label = le.fit_transform(label)
label = le.transform(label)

rand = RandomForestClassifier(n_estimators=1000, random_state=13)
y_oof, y_test_rand = k_folds(rand,ntv, label, tst, k=10)

ext = ExtraTreesClassifier(n_estimators=1000, random_state=13)
y_oof, y_test_ext = k_folds(ext, ntv, label, tst, k=10)

y_test = (y_test_ext + y_test_rand)/2#mean of the test giving by the two algo
y_test = np.argmax(y_test, axis=1)
career = pd.read_csv(os.path.join("sample_submission.csv"))
career['surface'] = le.inverse_transform(y_test)
career['surface'].replace({0:"fine_concrete",1:"concrete",
                2:"soft_tiles",3:"tiled",4:"soft_pvc",
                5:"hard_tiles_large_space",6:"carpet",7:"hard_tiles",8:"wood"},inplace=True)
career.to_csv('careercon.csv', index=False)