import asyncio
import pickle
from asyncua import ua, Server
import socket
import threading
import json
import struct

server_up=False

class asyncua_server():
    def __init__(self):
        self.server=None
        self.idx=None
        self.remote_server_idx=None
        self.variables={}

    async def delete_nodes(self,nodes):
        for node in nodes:
            node = "ns="+self.remote_server_idx+";i=" + node if node.isdigit() else "ns="+self.remote_server_idx+";s=" + node
            node = self.server.get_node(node)
            await self.server.delete_nodes([node])

    async def start_server(self,url,uri):
        # create a server
        self.server = Server()
        await self.server.init()
        # set endpoint information
        url = url
        self.server.set_endpoint(url)
        # create a new address space
        uri = uri
        self.idx = await self.server.register_namespace(uri)
        await self.server.start()
        try:
            # keep the server running until interrupted
            while True:
                await asyncio.sleep(0.1)
        finally:
            # stop the server
            await self.server.stop()


    async def update_variable(self,node,datatype):
        if (node.isdigit()):
            node_index = "ns="+self.remote_server_idx+";i=" + node
        else:
            node_index = "ns="+self.remote_server_idx+";s=" + node
        parent_node= self.server.get_node(self.variables[node]["parent"])

        datatype = int(datatype)
        if (datatype == 11):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], float(0))
            await self.variables[node]["node"].set_writable()
        elif (datatype == 6):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.Int32)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 1):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.Boolean)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 2):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.SByte)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 3):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.Byte)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 4):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.Int16)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 5):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.UInt16)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 7):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.UInt32)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 8):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.Int64)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 9):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.UInt64)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 10):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.Float)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 12):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.String)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 13):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.DateTime)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 14):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.Guid)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 15):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.ByteString)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 16):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.XmlElement)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 17):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.NodeId)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 18):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.ExpandedNodeId)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 19):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.StatusCode)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 20):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.QualifiedName)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 21):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.LocalizedText)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 22):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.ExtensionObject)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 23):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.DataValue)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 24):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0,ua.VariantType.Null)
            await self.variables[node]["node"].set_writable()
        elif (datatype == 25):
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], 0, ua.VariantType.DiagnosticInfo)
            await self.variables[node]["node"].set_writable()
        else:
            await self.variables[node]["node"].delete()
            self.variables[node]["node"] = await parent_node.add_variable(node_index, self.variables[node]["name"], None)




    async def add_object(self,node, parent_node, hierarchy):
        if (parent_node == None):
            if (node.isdigit()):
                node_index = "ns="+self.remote_server_idx+";i=" + node
            else:
                node_index = "ns="+self.remote_server_idx+";s=" + node
            await self.server.nodes.objects.add_folder(node_index, hierarchy["name"])
        else:
            if (parent_node.isdigit()):
                node_index = "ns="+self.remote_server_idx+";i=" + parent_node
            else:
                node_index = "ns="+self.remote_server_idx+";s=" + parent_node
            node_space = self.server.get_node(node_index)
            node_id="ns="+self.remote_server_idx+";i="+node if node.isdigit() else "ns="+self.remote_server_idx+";s="+node
            self.variables[node]={}
            self.variables[node]["parent"] = node_index
            self.variables[node]["name"] = hierarchy["name"]
            if(hierarchy["type"]==1):
                await node_space.add_object(node_id,hierarchy["name"])
            elif(hierarchy["type"]==2):
                if(hierarchy["datatype"]==11):
                    self.variables[node]["node"]= await node_space.add_variable(node_id,hierarchy["name"],float(0.0))
                    await self.variables[node]["node"].set_writable()
                elif(hierarchy["datatype"]==6):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], 0,ua.VariantType.Int32)
                    await self.variables[node]["node"].set_writable()
                elif (hierarchy["datatype"] == 1):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"],True,
                                                                                 ua.VariantType.Boolean)
                    await self.variables[node]["node"].set_writable()
                elif (hierarchy["datatype"] == 2):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], 0,
                                                                                 ua.VariantType.SByte)
                    await self.variables[node]["node"].set_writable()
                elif (hierarchy["datatype"] == 3):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], 0,
                                                                                 ua.VariantType.Byte)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 4):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], 0,
                                                                                 ua.VariantType.Int16)
                    await self.variables[node]["node"].set_writable()
                elif (hierarchy["datatype"] == 5):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], 0,
                                                                                 ua.VariantType.UInt16)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 7):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], 0,
                                                                                 ua.VariantType.UInt32)
                    await self.variables[node]["node"].set_writable()
                elif (hierarchy["datatype"] == 8):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], 0,
                                                                                 ua.VariantType.Int64)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 9):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], 0,
                                                                                 ua.VariantType.UInt64)
                    await self.variables[node]["node"].set_writable()
                elif (hierarchy["datatype"] == 10):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], float(0),
                                                                                 ua.VariantType.Float)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 12):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], "",
                                                                                 ua.VariantType.String)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 13):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], "March 17, 2023 12:34:56 PM",
                                                                                 ua.VariantType.DateTime)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 14):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], "8d2b1fc4-af4f-4a21-a4d0-4ad51d76c0b7",
                                                                                 ua.VariantType.Guid)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 15):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], b"",
                                                                                 ua.VariantType.ByteString)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 16):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], "<root><node>value</node></root>",
                                                                                 ua.VariantType.XmlElement)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 17):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], "ns=1;i=1001",
                                                                                 ua.VariantType.NodeId)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 18):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], "ns=1;i=1001",
                                                                                 ua.VariantType.ExpandedNodeId)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 19):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], 0,
                                                                                 ua.VariantType.StatusCode)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 20):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], "Myvar",
                                                                                 ua.VariantType.QualifiedName)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 21):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], "Localized text in English",
                                                                                 ua.VariantType.LocalizedText)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 22):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], {},
                                                                                 ua.VariantType.ExtensionObject)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 23):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], 0,
                                                                                 ua.VariantType.DataValue)
                    await self.variables[node]["node"].set_writable
                elif (hierarchy["datatype"] == 24):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], 0, ua.VariantType.Null)
                    await self.variables[node]["node"].set_writable()
                elif (hierarchy["datatype"] == 25):
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], 0, ua.VariantType.DiagnosticInfo)
                    await self.variables[node]["node"].set_writable()
                else:
                    self.variables[node]["node"] = await node_space.add_variable(node_id, hierarchy["name"], None)
                    await self.variables[node]["node"].set_writable()

    async def update_datatypes(self,datatypes):
        for data in datatypes:
            node,datatype=data.split("-")
            await self.update_variable(node,datatype)

asr = asyncua_server()
identifier_array_received=[]
identifier_array_existing=[]
received_datatypes=[]
existing_datatypes=[]
def get_received_hierarchy_array(hierarchy):
    for key in hierarchy:
        if(key!="hash" and key!="namespace_idx"):
            if(isinstance(hierarchy[key],dict)):
                if('datatype' in hierarchy[key]):
                    received_datatypes.append(key+"-"+str(hierarchy[key]["datatype"]))
                identifier_array_received.append(key)
                get_received_hierarchy_array(hierarchy[key])
    return identifier_array_received,received_datatypes

async def get_existing_hierarchy_array(client,node_objects):
    node_hierarchy = {}
    for sub_obj in node_objects:
        if ("ns="+asr.remote_server_idx+";" in str(sub_obj)):
            node = client.get_node(sub_obj)
            children_nodes = await node.get_children()
            identifier = str(children_nodes[0].nodeid.Identifier) if children_nodes else ""
            node_class = await sub_obj.read_node_class()
            if (identifier not in str(sub_obj)):
                identifier_name = sub_obj.nodeid.Identifier
                identifier_array_existing.append(str(identifier_name))
                node_hierarchy[identifier_name] = await get_existing_hierarchy_array(client, children_nodes)
                if(node_class.name == "Variable"):
                    datatype = await sub_obj.read_data_type_as_variant_type()
                    if (datatype._value_ == 0):
                        existing_datatypes.append(str(identifier_name) + "-" + str(24))
                    else:
                        existing_datatypes.append(str(identifier_name) + "-" + str(datatype._value_))

            else:
                identifier_name = sub_obj.nodeid.Identifier
                identifier_array_existing.append(str(identifier_name))
                if (node_class.name == "Variable"):
                    datatype = await sub_obj.read_data_type_as_variant_type()
                    if(datatype._value_==0):
                        existing_datatypes.append(str(identifier_name) + "-" + str(24))
                    else:
                        existing_datatypes.append(str(identifier_name) + "-" + str(datatype._value_))
    return identifier_array_existing,existing_datatypes

def add_nodes(nodes_to_add,hierarchy,parent_key=None):
    for key in hierarchy:
        if (key != "hash" and key!="namespace_idx"):
            if (isinstance(hierarchy[key], dict)):
                if (key in nodes_to_add):
                    asyncio.run(asr.add_object(key, parent_key,hierarchy[key]))
                add_nodes(nodes_to_add,hierarchy[key],key)

def analyse_hierarchy(hierarchy):
    asr.remote_server_idx = str(hierarchy["namespace_idx"])
    array1,datatypes1=get_received_hierarchy_array(hierarchy)
    root_node=asr.server.get_root_node()
    object_root_node=asyncio.run(root_node.get_children())
    node_objects= asyncio.run(object_root_node[0].get_children())
    array2,datatypes2=asyncio.run(get_existing_hierarchy_array(asr.server,node_objects))
    nodes_to_add=set(array1)-set(array2)
    nodes_to_delete=set(array2)-set(array1)
    datatypes_to_update=set(datatypes1)-set(datatypes2)
    add_nodes(nodes_to_add,hierarchy)
    asyncio.run(asr.delete_nodes(nodes_to_delete))
    if(datatypes_to_update and not(nodes_to_add) and not(nodes_to_delete)):
        asyncio.run(asr.update_datatypes(datatypes_to_update))
    identifier_array_received.clear()
    received_datatypes.clear()
    identifier_array_existing.clear()
    existing_datatypes.clear()

def hash_receive(udp_receiver_ip,udp_receiver_port):
    prev_hash = 0
    localIP = udp_receiver_ip
    localPort = udp_receiver_port
    bufferSize = 1024
    UDPServerSocket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
    UDPServerSocket.bind((localIP, localPort))
    while (True):
        bytesAddressPair = UDPServerSocket.recvfrom(bufferSize)
        message = bytesAddressPair[0]
        json_string = message.decode()
        hierarchy = json.loads(json_string)
        if(prev_hash!=hierarchy["hash"] and asr.idx):
            analyse_hierarchy(hierarchy)
            prev_hash=hierarchy["hash"]

def st(endpoint_url,namespace_server,udp_hash_receiver_ip,udp_hash_receiver_port):
    t1=threading.Thread(target=hash_receive,args=(udp_hash_receiver_ip,udp_hash_receiver_port))
    t1.start()
    asyncio.run(asr.start_server(endpoint_url,namespace_server))