#! /usr/bin/env python
# -*- coding: utf-8 -*-

import harklib

from . import dispatch

import typing
import types

class NodeUserData(harklib.BaseNodeUserData):
    def __init__(self, node, nodedef):
        super().__init__()
        self._node = node
        self._nodedef = nodedef

    def get_def(self):
        return self._nodedef

class NetworkUserData(harklib.BaseNodeUserData):
    def __init__(self, network, networkdef):
        super().__init__()
        self._network = network
        self._networkdef = networkdef

    def get_def(self):
        return self._networkdef

class BaseDispatcher:
    def __init__(self, dispatcher: harklib.BaseDispatcher):
        self._inner = dispatcher

    def inner(self):
        return self._inner

    def detach(self):
        _i = self._inner
        self._inner = None
        return _i

class TriggeredRepeatDispatcher(BaseDispatcher):
    def __init__(self):
        super().__init__(harklib.TriggeredRepeatDispatcher())

class TriggeredOneShotDispatcher(BaseDispatcher):
    def __init__(self):
        super().__init__(harklib.TriggeredOneShotDispatcher())

class TriggeredMultiShotDispatcher(BaseDispatcher):
    def __init__(self):
        super().__init__(harklib.TriggeredMultiShotDispatcher())

class OneShotDispatcher(BaseDispatcher):
    def __init__(self):
        super().__init__(harklib.OneShotDispatcher())

class RepeatDispatcher(BaseDispatcher):
    def __init__(self):
        super().__init__(harklib.RepeatDispatcher())

class DataSource:
    def __init__(self, data_source: harklib.DataSource):
        self._inner = data_source

    def inner(self):
        return self._inner

class DataSink:
    def __init__(self, data_sink: harklib.DataSink):
        self._inner = data_sink

    def inner(self):
        return self._inner

class DataSourceMap:
    def __init__(self, data_source_map: harklib.DataSourceMap):
        assert(isinstance(data_source_map, harklib.DataSourceMap))
        self._inner = data_source_map

    def __getitem__(self, name: str) -> DataSource:
        # print('DataSourceMap.__getitem__: self: {}, name: {}'.format(self, name))
        assert(isinstance(self, DataSourceMap))
        assert(isinstance(self._inner, harklib.DataSourceMap))
        return DataSource(self._inner.get(name))

class DataSinkMap:
    def __init__(self, data_sink_map: harklib.DataSinkMap):
        self._inner = data_sink_map

    def add_input(self, name: str, source: DataSource) -> 'DataSinkMap':
        self._inner.add_input(name, source.inner())
        return self

class NetworkDefWrapper(harklib.NetworkDef):
    def __init__(self, ndef: 'NetworkDef'):
        super().__init__()
        self._def = ndef

    def build(self, network: harklib.Network, input: harklib.DataSourceMap, output: harklib.DataSinkMap):
        assert(isinstance(network, harklib.Network))
        assert(isinstance(input, harklib.DataSourceMap))
        assert(isinstance(output, harklib.DataSinkMap))
        self._def.build(Network(network), DataSourceMap(input), DataSinkMap(output))

class NodeDef:
    def __init__(self, node_def: harklib.NodeDef):
        self._inner = node_def

    def inner(self):
        return self._inner

class NetworkDef:
    def __init__(self):
        self._inner = NetworkDefWrapper(self)

    def build(self, network: 'Network', input: DataSourceMap, output: DataSinkMap):
        pass

    def inner(self):
        return self._inner

class Node:
    def __init__(self, node: harklib.BaseNode, nodedef: 'NodeDef' = None, dispatch: 'BaseDispatcher' = None):
        self._inner = node
        self._nodedef = nodedef
        self._dispatch = dispatch

    @classmethod
    def create(self, ndef: typing.Type[NodeDef], dispatch: typing.Type[BaseDispatcher] = TriggeredOneShotDispatcher, name: str = None):
        if name is None:
            name = ndef.__name__
        dp = dispatch()
        nd = harklib.Node(ndef().inner(), dp.inner(), name)
        return Node(nd, dp.inner(), name)

    def __call__(self, *args, **kwargs):
        return self._inner(*args, **kwargs)

    # def __del__(self):
    #     print('Node.__del__')
    #     s = super()
    #     if hasattr(s, '__del__'):
    #         s.__del__(self)
    #     if self._inner:
    #         self._inner.detach_userdata()

    def __getitem__(self, name: str):
        return DataSource(self._inner.get(name))

    # def add_input(self, name: str, source: DataSource) -> 'Node':
    #     self._inner.add_input(name, source.inner())
    #     return self

    def add_input(self, name: str, value: typing.Union[DataSource, str, float, int, bool]) -> 'Node':
        if isinstance(value, DataSource):
            self._inner.add_input(name, value.inner())
        else:
            self._inner.add_input(name, value)
        return self

    def attach_userdata(self, user_data: NodeUserData):
        self._inner.attach_userdata(user_data)
        self._user_data = user_data

class Network:
    def __init__(self, network: harklib.Network, networkdef: 'NetworkDef' = None, dispatch: 'BaseDispatcher' = None):
        self._inner = network
        self._networkdef = networkdef
        self._dispatch = dispatch

    # def __del__(self):
    #     print('Network.__del__')
    #     s = super()
    #     if hasattr(s, '__del__'):
    #         s.__del__(self)
    #     if self._inner:
    #         self._inner.detach_userdata()

    @classmethod
    def from_networkdef(self, ndef: typing.Type[NetworkDef], dispatch: typing.Type[BaseDispatcher] = TriggeredOneShotDispatcher, name: str = None):
        if name is None:
            name = ndef.__name__
        nd = ndef()
        dp = dispatch()
        return Network(harklib.Network.create_network(nd.inner(), dp.inner(), name), nd, dp)

    def create(self, ndef: typing.Union[typing.Type[NodeDef], typing.Type[NetworkDef]], dispatch: typing.Type[BaseDispatcher] = TriggeredOneShotDispatcher, name: str = None) -> typing.Any:
        if name is None:
            name = ndef.__name__
        nd = ndef()
        dp = dispatch()

        if issubclass(ndef, NodeDef):
            bn = self._inner.create(nd.inner(), dp.inner(), name)
            node = Node(bn, nd, dp)
            node.attach_userdata(NodeUserData(node, nd))
            return node

        elif issubclass(ndef, NetworkDef):
            bn = self._inner.create(nd.inner(), dp.inner(), name)
            network = Network(bn, nd, dp)
            network.attach_userdata(NetworkUserData(network, nd))
            return network

    def query_nodedef(self, name: str) -> 'NodeDef':
        node = self._inner.query_node(name)
        if node:
            return node.get_userdata().get_def()
        return None

    def add_input(self, name: str, value: typing.Union[DataSource, str, float, int, bool]) -> 'Network':
        if isinstance(value, DataSource):
            self._inner.add_input(name, value.inner())
        else:
            self._inner.add_input(name, value)
        return self

    def attach_userdata(self, user_data: NetworkUserData):
        self._inner.attach_userdata(user_data)
        self._user_data = user_data

    def __getitem__(self, name: str):
        return DataSource(self._inner.get(name))

    def execute(self):
        self._inner.execute()

    def stop(self):
        self._inner.stop()

    def __call__(self, *args, **kwargs):
        return self._inner(*args, **kwargs)

from . import node

# end of file
