import paraview.modules.vtkRemotingMicroservices
import paraview.modules.vtkRemotingServerManagerDefinitions
from paraview.modules.vtkRemotingServerManager import vtkClientSession, vtkSMProxy
from paraview.modules.vtkRemotingPythonAsyncCore import (
    vtkPythonObservableWrapperUtilities,
)
from paraview.modules.vtkRemotingServerManagerCore import vtkSMPropertyTypes
from paraview.modules.vtkUIGenerationCore import vtkLayoutGenerator

PROPERTY_TYPES = {
    vtkSMPropertyTypes.INVALID: "none",
    vtkSMPropertyTypes.INT: "int32",
    vtkSMPropertyTypes.DOUBLE: "float64",
    vtkSMPropertyTypes.ID_TYPE: "int32",
    vtkSMPropertyTypes.STRING: "string",
    vtkSMPropertyTypes.PROXY: "proxy",
    vtkSMPropertyTypes.INPUT: "proxy",
}


class ProxyAdapter(
    paraview.modules.vtkRemotingServerManagerDefinitions.vtkProxyAdapter
):
    def as_dict(self):
        group, name = self.GetGroup(), self.GetName()
        _tags = []
        proxy_type = f"{group}__{name}"
        proxy_definition = {"_tags": _tags}

        _ordered_groups = []
        _ordered_properties = []

        for group in self.GetPropertyGroups():
            _ordered_groups.append(group.GetName())
            group_definition = {
                "label": group.GetXMLLabel(),
                "widget": group.GetPanelWidget(),
                "visibility": group.GetPanelVisibility(),
            }
            proxy_definition[f"_group_{group.GetName()}"] = group_definition

        for property in self.GetProperties():
            property_definition = ProxyAdapter._get_property_adapter_as_dict(property)

            if property_definition:
                proxy_definition[property.GetName()] = property_definition
                _ordered_properties.append(property.GetName())

        proxy_definition["_ordered_properties"] = _ordered_properties
        proxy_definition["_ordered_groups"] = _ordered_groups
        return {proxy_type: proxy_definition}

    @staticmethod
    def _get_property_domains_as_dict(property):
        result = []

        for domain in property.GetDomains():
            domain_class = domain.GetClassName()
            domain_name = domain.GetName()

            # keep, name, widget, ui_attributes = domains.get_domain_widget(domain)
            domain_entry = {
                "name": domain_name,
                "type": "ParaViewDomain",
                "pv_class": domain_class,
                "pv_name": domain_name,
                # "widget": widget,
                # "pv_widget": widget, move logic from pqProxyWidget or show propertyproxy wiget if it os a special case
                # "ui_attributes": ui_attributes, # look into pqProxyWidget each case
            }
            # if keep:
            #    result.append(domain_entry)
            result.append(domain_entry)

        return result

    @staticmethod
    def _get_property_adapter_as_dict(property):
        property_definition = {}

        # if (
        #    property.GetInformationOnly()
        #    or property.GetIsInternal()
        #    # or property.GetClassName() not in PROPERTY_TYPES
        # ):
        if property.GetPanelVisibility() == "never" or property.GetIsInternal():
            return False

        # if len(property.GetLabel()) > 0:
        property_definition["_label"] = property.GetLabel()
        property_definition["_group"] = (
            property.GetPropertyGroup().GetName()
            if property.GetPropertyGroup()
            else None
        )
        property_definition["_help"] = property.GetDocumentationDescriptionAsString()
        property_definition["type"] = PROPERTY_TYPES[property.GetType()]
        property_definition["size"] = property.GetSize()
        property_definition["ui_attributes"] = {
            "panel_visibility": property.GetPanelVisibility(),
            "widget": property.GetPanelWidget().GetWidgetClassName(),
            "layout": property.GetPanelWidget().GetWidgetLayout(),
            "decorators": list(property.GetDecorators()),
        }
        print("decorators", *property.GetDecorators())

        # Skip proxy property with n proxies (??? FIXME ???)
        # currently simput don't properly manage list of proxy as property...
        # i.e. views.RenderView.Representations
        # if property_definition["type"] == "proxy" and property_definition["size"] != 1:
        #    print(
        #        "Skip multi-proxy property",
        #        # property.GetParent().GetXMLName(), // TODO
        #        property.GetName(),
        #    )
        #    return False

        # Domains
        property_definition["domains"] = [
            *ProxyAdapter._get_property_domains_as_dict(property),
        ]
        #    *property_domains_yaml(property),
        #    *merge_decorators(
        #        *property_widget_decorator_yaml(property),
        #        *property_widget_decorator_advanced_yaml(property),
        #    ),
        # ]

        return property_definition


class ProxyListIterable:
    """Transform an iterable of vtkSMProxies to an iterable that returns
    Tuple[groupName:str, proxyName:str]"""

    def __init__(self, prototypes):
        self._iter = iter(prototypes)

    def __iter__(self):
        return self

    def __next__(self):
        next_item = self._iter.__next__()
        return next_item.GetXMLGroup(), next_item.GetXMLName()


class ProxyDefinitionIterable:
    """Iterable helper for proxy definitions. It is created internally in
    DefinitionManager.GetAvailableDefinitions()."""

    def __init__(self, prototypes_async_iter):
        self._iter = prototypes_async_iter

    def __aiter__(self):
        return self

    async def __anext__(self):
        next_value = await self._iter.__anext__()
        return ProxyListIterable(next_value)


class DefinitionManager(
    paraview.modules.vtkRemotingMicroservices.vtkPTSDefinitionManager
):
    """
    A thin Python Wrapper around vtkPTSProxyDefintionManager
    """

    def __init__(self, session):

        if not isinstance(session, vtkClientSession):
            raise TypeError(" 'session' argument is not of type vtkClientSession")

        super().SetSession(session)

    def GetDefinition(self, *args):
        """
        Get the definition of a proxy.
        Args:
          If one argument is passed it should be an instance of vtkSMProxy.

          If two arguments are passed they should be strings coresponding to
          the group and name of the proxy definition.

        Returns:
            A ProxyAdapter object holding all information related to the proxy's definition.
            See C++ implementation of vtkProxyAdapter for available methods.
        """
        definition = None
        if len(args) == 1 and isinstance(args[0], vtkSMProxy):
            definition = super().GetDefinition(args[0])
        elif len(args) == 2 and isinstance(args[0], str) and isinstance(args[1], str):
            definition = super().GetDefinition(args[0], args[1])
        else:
            types = [type(arg) for arg in args]
            raise RuntimeError(f"Unsupported argument types {*types,}")

        result = ProxyAdapter()
        # we do not yet Python overrides yet so we construct a new ProxyAdapter
        result.Move(definition)
        return result

    def GetLayout(self, *args):
        """
        Get the xml layout of the proxy definition.
        Args:
          If one argument is passed it should be an instance of vtkSMProxy.

          If two arguments are passed they should be strings coresponding to
          the group and name of the proxy definition.

        Returns:
            A vtkPVXMLElement TODO add more details.

        Note:
          Use DefinitionManager.vtkPVXMLElement_to_string( ) to serialize the object into a string.
        """
        if len(args) == 1 and isinstance(args[0], vtkSMProxy):
            return super().GetLayout(args[0])
        elif len(args) == 2 and isinstance(args[0], str) and isinstance(args[1], str):
            return super().GetLayout(args[0], args[1])
        else:
            types = [type(arg) for arg in args]
            raise RuntimeError(f"Unsupported argument types {*types,}")

    def GetCompatibleDefinitions(self, proxies, group_name):
        """Get an iterable of Tuple[groupName:str, proxyName:str] of all proxies in `groupName` that can accept `proxies` as input.
        If proxies is a list they are considered as multiple inputs to the 0th connection"""
        if isinstance(proxies, vtkSMProxy):
            proxies = [proxies]
        return ProxyListIterable(super().GetCompatibleDefinitions(proxies, group_name))

    def GetDefinitionsObservable(self, group_name=""):
        """Get an Iterable of Ttuples (groupName,proxyName) for all available proxy definitions.
        If group_name is provided , iteration is restricted within group_name"""

        if len(group_name) > 0:
            prototypes = vtkPythonObservableWrapperUtilities.GetIterator(
                super().GetDefinitionsObservable(group_name)
            )
        else:
            prototypes = vtkPythonObservableWrapperUtilities.GetIterator(
                super().GetDefinitionsObservable()
            )

        return ProxyDefinitionIterable(prototypes)

    @staticmethod
    def vtkPVXMLElement_to_string(xmlElement):
        return vtkLayoutGenerator.XMLToString(xmlElement)
