from common import node
from KL.types import NodeId, LocalizedText, ValueRank, AccessLevel, QualifiedName
from KL.constants import ObjectIds
from KL.application_protocol import AddNodesItem, NodeClass, VariableAttributes, Variant, VariantType, ObjectAttributes
from common import instantiate

def _parse_nodeid_qname(*args):
    try:
        if isinstance(args[0], int):
            nodeid = NodeId(0, int(args[0]))
            qname = QualifiedName(args[1], int(args[0]))
            return nodeid, qname
        if isinstance(args[0], NodeId):
            nodeid = args[0]
        elif isinstance(args[0], str):
            nodeid = NodeId.from_string(args[0])
        else:
            raise RuntimeError()
        if isinstance(args[1], QualifiedName):
            qname = args[1]
        elif isinstance(args[1], str):
            qname = QualifiedName.from_string(args[1])
        else:
            raise RuntimeError()
        return nodeid, qname
    except Exception as ex:
        raise TypeError(
            "This method takes either a namespace index and a string as argument or a nodeid and a qualifiedname. Received arguments {0} and got exception {1}".format(
                args, ex))


def create_object(parent, nodeid, bname, objecttype=None):
    nodeid, qname = _parse_nodeid_qname(nodeid, bname)
    if objecttype is not None:
        objecttype = node.Node(parent.server, objecttype)
        dname = LocalizedText(qname.Name)
        nodes = instantiate.instantiate(parent, objecttype, nodeid, bname=qname, dname=dname)[0]
        return nodes
    else:
        return node.Node(parent.server,
                         _create_object(parent.server, parent.nodeid, nodeid, qname, ObjectIds.BaseObjectType))


def create_variable(parent, nodeid, bname, val, varianttype=None, datatype=None):
    nodeid, qname = _parse_nodeid_qname(nodeid, bname)
    var = Variant(val, varianttype)
    if datatype and isinstance(datatype, int):
        datatype = NodeId(datatype, 0)
    if datatype and not isinstance(datatype, NodeId):
        raise RuntimeError("datatype argument must be a nodeid or an int refering to a nodeid")

    return node.Node(parent.server,
                     _create_variable(parent.server, parent.nodeid, nodeid, qname, var, datatype=datatype,
                                      isproperty=False))


def _create_variable(server, parentnodeid, nodeid, qname, var, datatype=None, isproperty=False):
    addnode = AddNodesItem()
    addnode.RequestedNewNodeId = nodeid
    addnode.BrowseName = qname
    addnode.NodeClass = NodeClass.Variable
    addnode.ParentNodeId = parentnodeid
    if isproperty:
        addnode.ReferenceTypeId = NodeId(ObjectIds.HasProperty)
        addnode.TypeDefinition = NodeId(ObjectIds.PropertyType)
    else:
        addnode.ReferenceTypeId = NodeId(ObjectIds.HasComponent)
        addnode.TypeDefinition = NodeId(ObjectIds.BaseDataVariableType)
    attrs = VariableAttributes()
    attrs.Description = LocalizedText(qname.Name)
    attrs.DisplayName = LocalizedText(qname.Name)
    if datatype:
        attrs.DataType = datatype
    else:
        attrs.DataType = _guess_datatype(var)

    attrs.Value = var
    if not isinstance(var.Value, (list, tuple)):
        attrs.ValueRank = ValueRank.Scalar
    else:
        if var.Dimensions:
            attrs.ValueRank = len(var.Dimensions)
            attrs.ArrayDimensions = var.Dimensions
    attrs.WriteMask = 0
    attrs.UserWriteMask = 0
    attrs.Historizing = False
    attrs.AccessLevel = AccessLevel.CurrentRead.mask
    attrs.UserAccessLevel = AccessLevel.CurrentRead.mask
    addnode.NodeAttributes = attrs
    results = server.add_nodes([addnode])
    results[0].StatusCode.check()
    return results[0].AddedNodeId


def _create_object(server, parentnodeid, nodeid, qname, objecttype):
    addnode = AddNodesItem()
    addnode.RequestedNewNodeId = nodeid
    addnode.BrowseName = qname
    addnode.ParentNodeId = parentnodeid
    if node.Node(server, parentnodeid).get_type_definition() == NodeId(ObjectIds.FolderType):
        addnode.ReferenceTypeId = NodeId(ObjectIds.Organizes)
    else:
        addnode.ReferenceTypeId = NodeId(ObjectIds.HasComponent)
    addnode.NodeClass = NodeClass.Object
    if isinstance(objecttype, int):
        addnode.TypeDefinition = NodeId(objecttype)
    else:
        addnode.TypeDefinition = objecttype
    attrs = ObjectAttributes()
    attrs.EventNotifier = 0

    attrs.Description = LocalizedText(qname.Name)
    attrs.DisplayName = LocalizedText(qname.Name)
    attrs.WriteMask = 0
    attrs.UserWriteMask = 0
    addnode.NodeAttributes = attrs
    results = server.add_nodes([addnode])
    results[0].StatusCode.check()
    return results[0].AddedNodeId


def _guess_datatype(variant):
    if variant.VariantType == VariantType.ExtensionObject:
        if variant.Value is None:
            raise Exception("Cannot guess DataType from Null ExtensionObject")
        if type(variant.Value) in (list, tuple):
            if len(variant.Value) == 0:
                raise Exception("Cannot guess DataType from Null ExtensionObject")
            extobj = variant.Value[0]
        else:
            extobj = variant.Value
        classname = extobj.__class__.__name__
        return NodeId(getattr(ObjectIds, classname))
    else:
        return NodeId(getattr(ObjectIds, variant.VariantType.name))
