import grpc
import gzip
import json
import enum
import threading
from google.protobuf import json_format
from robomotion import plugin_pb2
from robomotion.plugin_pb2_grpc import RuntimeHelperStub
from robomotion.struct_pb2 import Struct
from robomotion.message import Context

from types import SimpleNamespace as Namespace


class Runtime:
    active_nodes = 0
    client: RuntimeHelperStub = None
    event: threading.Event = threading.Event()
    factories = {}
    nodes = {}

    @staticmethod
    def set_client(client: RuntimeHelperStub):
        Runtime.client = client

    @staticmethod
    def check_runner_conn(connection: grpc.Channel):
        def cb(state: grpc.ChannelConnectivity):
            if state == grpc.TRANSIENT_FAILURE or state == grpc.SHUTDOWN:
                Runtime.event.set()

        connection.subscribe(cb, True)

    @staticmethod
    def create_node(name: str, factory):
        Runtime.factories[name] = factory

    @staticmethod
    def add_node(guid: str, node):
        Runtime.nodes[guid] = node

    @staticmethod
    def compress(data: bytes):
        return gzip.compress(data)

    @staticmethod
    def decompress(data: bytes):
        return gzip.decompress(data)

    @staticmethod
    def deserialize(data: bytes, c):
        node = c()
        obj = json.loads(data, object_hook=lambda d: Namespace(**d))
        for key in obj.__dict__.keys():
            node.__setattr__(key, obj.__dict__[key])
        return node

    @staticmethod
    def close():
        if Runtime.client is None:
            return

        request = plugin_pb2.Empty()
        Runtime.client.Close(request)

    @staticmethod
    def get_variable(variable: Runtime.Variable, ctx: Context):
        scope = variable.scope
        name = variable.name

        if scope == 'Custom':
            return name

        if scope == 'Message':
            msg = json.loads(ctx.get_raw().decode('utf-8'))
            if name in msg:
                return msg[name]
            else:
                return None

        if Runtime.client is None:
            return None

        var = plugin_pb2.Variable(scope=scope, name=name)
        request = plugin_pb2.GetVariableRequest(variable=var)
        response = Runtime.client.GetVariable(request)

        return json_format.MessageToDict(response.value)['value']

    @staticmethod
    def set_variable(variable: Runtime.Variable, ctx: Context, value: object):
        scope = variable.scope
        name = variable.name

        if scope == 'Message':
            ctx.set(name, value)
            return

        if Runtime.client is None:
            return

        val = Struct()
        val.update({'value': value})

        var = plugin_pb2.Variable(scope=scope, name=name)
        request = plugin_pb2.SetVariableRequest(variable=var, value=val)
        Runtime.client.SetVariable(request)
		
		
    class _DefVal:
        def __init__(self, default: object):
            self.default = default

        def __init__(self, scope: str, name: str):
            self.default = {scope: scope, name: name}


    class _Enum:
        def __init__(self, enums: [], enumNames: []):
            self.__enums = enums
            self.__enumNames = enumNames

        @property
        def enums(self):
            return self.__enums

        @property
        def enumNames(self):
            return self.__enumNames


    class Variable:
        def __init__(self, scope: str='', name: str='', title: str='', type: str='', pyScope: bool=False, customScope: bool=False,
                     messageScope: bool=False, messageOnly: bool=False, hidden: bool=False, input: bool=False, output: bool=False,
                     option: bool=False, default: _DefVal=None, enum: _Enum=None):

            self.__scope = scope
            self.__name = name
            self.__title = title
            self.__type = type
            self.__pyScope = pyScope
            self.__customScope = customScope
            self.__messageScope = messageScope
            self.__messageOnly = messageOnly
            self.__hidden = hidden
            self.__input = input
            self.__output = output
            self.__option = option
            self.__default = default
            self.__enum = enum

        @property
        def scope(self) -> str:
            return self.__scope

        @property
        def name(self) -> str:
            return self.__name

        @property
        def title(self) -> str:
            return self.__title

        @property
        def type(self) -> str:
            return self.__type

        @property
        def pyScope(self) -> str:
            return self.__pyScope

        @property
        def customScope(self) -> str:
            return self.__customScope

        @property
        def messageScope(self) -> str:
            return self.__messageScope

        @property
        def messageOnly(self) -> str:
            return self.__messageOnly

        @property
        def hidden(self) -> str:
            return self.__hidden

        @property
        def __input(self) -> str:
            return self.____input

        @property
        def output(self) -> str:
            return self.__output

        @property
        def option(self) -> str:
            return self.__option

        @property
        def default(self) -> str:
            return self.__default

        @property
        def enum(self) -> str:
            return self.__enum


    class InVariable(Variable):
        def get(self, ctx: Context):
            return Runtime.get_variable(self, ctx)


    class OutVariable(Variable):
        def set(self, ctx: Context, value: object):
            Runtime.set_variable(self, ctx, value)


    class OptVariable(Variable):
        def get(self, ctx: Context):
            return Runtime.get_variable(self, ctx)


    class ECategory(enum.Enum):
        Null = 0,
        Login = 1
        Email = 2
        CreditCard = 3
        Token = 4
        Database = 5
        Document = 6


    class Credentials:
        def __init__(self, vaultId: str='', itemId: str='', title: str='', category: ECategory=ECategory.Null):
            self.__vaultId = vaultId
            self.__itemId = itemId
            self.__title = title
            self.__category = category

        @property
        def vaultId(self) -> str:
            return self.__vaultId

        @property
        def itemId(self) -> str:
            return self.__itemId

        @property
        def title(self) -> str:
            return self.__title

        @property
        def category(self) -> ECategory:
            return self.__category

        def get_vault_item(self):
            if Runtime.client is None:
                return {}

            request = plugin_pb2.GetVaultItemRequest(vaultId=self.vaultId, ItemId=self.itemId)
            response = Runtime.client.GetVaultItem(request)
            return json_format.MessageToDict(response.item)['value']
