import paraview.modules.vtkRemotingApplication
from paraview.modules.vtkRemotingServerManager import (
    vtkClientSession,
)  # for vtkClientSession.DATA_SERVER / RENDER_SERVER
from paraview.modules.vtkRemotingPythonAsyncCore import (
    vtkPythonObservableWrapperUtilities,
)

from paraview.modules.vtkRemotingServerManagerViews import (
    vtkSMParaViewPipelineControllerWithRendering,
)


class PropertyManager(paraview.modules.vtkRemotingApplication.vtkPTSPropertyManager):
    """Set and get properties of a proxy.

    PropertyManager is a thin wrapper of vtkPTSPropertyManager that exposes a more pythonic interface.
    """

    def SetPropertyValue(self, proxy, propertyName, value):
        """Set a single property of a proxy

        Example:
          propertyManager.SetPropertyValue(sphere, 'Radius' , 4 )

        """
        if isinstance(value, (list, tuple)):
            for i, v in enumerate(value):
                self.SetPropertyValueElement(proxy, propertyName, v, i)
        else:
            self.SetPropertyValueElement(proxy, propertyName, value, 0)
        proxy.UpdateVTKObjects()

    def SetProperties(self, proxy, value):
        """Set properties of a proxy using a change set represented as a dictionary.

        Example:
          propertyManager.SetProperties(sphere, { 'Center' : [1,2,3], 'Radius' : 4 })

        """
        for key, value in value.items():
            self.SetPropertyValue(proxy, key, value)

    def GetPropertyValue(self, proxy, propertyName):
        """Get the value of a propery

        Example:
          propertyManager.GetPropertyValue(sphere, 'Radius') - >  0.5
          propertyManager.GetPropertyValue(sphere, 'Center') - >  [0,0,0]
        """
        numberOfElements = proxy.GetProperty(propertyName).GetNumberOfElements()
        if numberOfElements > 1:
            result = []
            for i in range(numberOfElements):
                result.append(
                    PropertyManager._vtkVariantToPythonObject(
                        self.GetPropertyValueElement(proxy, propertyName, i)
                    )
                )
            return result
        else:
            return PropertyManager._vtkVariantToPythonObject(
                self.GetPropertyValueElement(proxy, propertyName, 0)
            )

    def GetProperties(self, proxy):
        """Get all properties values of a proxy as a dictionary

        Example:
          propertyManager.GetProperties(sphere) -> { 'Center' : [0,0,0], 'Radius' : 0.5, ... }
        """
        iterator = proxy.NewPropertyIterator()
        iterator.Begin()
        result = dict()
        while not iterator.IsAtEnd():
            name = iterator.GetKey()
            value = self.GetPropertyValue(proxy, name)
            result[name] = value
            iterator.Next()
        del iterator
        return result

    async def Update(self, proxy):
        """Python async wrapper for calling rxcpp::observable<bool> proxy.Update()"""
        return await vtkPythonObservableWrapperUtilities.GetFuture(proxy.Update())

    async def UpdatePipeline(self, proxy, time=0.0):
        """Python async wrapper for calling rxcpp::observable<bool> proxy.UpdatePipeline(time)"""
        return await vtkPythonObservableWrapperUtilities.GetFuture(
            proxy.UpdatePipeline(time)
        )

    async def UpdateInformation(self, proxy):
        """Python async wrapper for calling rxcpp::observable<bool> proxy.UpdateInformation()"""
        return await vtkPythonObservableWrapperUtilities.GetFuture(
            proxy.UpdateInformation()
        )

    async def ExecuteCommand(
        self,
        proxy,
        name,
        destinationMask=vtkClientSession.DATA_SERVER | vtkClientSession.RENDER_SERVER,
    ):
        """Python async wrapper for calling rxcpp::observable<bool> proxy.ExecuteCommand(name,destinationMask)"""
        return await vtkPythonObservableWrapperUtilities.GetFuture(
            proxy.ExecuteCommand(name, destinationMask)
        )

    def SetVisibility(self, representation, view, visibility):
        """Set visibility of `representation` in `view`"""

        controller = vtkSMParaViewPipelineControllerWithRendering()
        if representation.GetXMLGroup() != "representations":
            raise RuntimeError("First argument is not a representation proxy")
        if view.GetXMLGroup() != "views":
            raise RuntimeError("First argument is not a view proxy")
        if visibility:
            controller.Show(representation, view)
        else:
            controller.Hide(representation, view)

    @staticmethod
    def _vtkVariantToPythonObject(variant):
        """Convert a variant to its base type.

        TODO can we make this less verbose ?"""
        if variant.IsString():
            return variant.ToString()
        elif variant.IsFloat():
            return variant.ToFloat()
        elif variant.IsDouble():
            return variant.ToDouble()
        elif variant.IsChar():
            return variant.ToChar()
        elif variant.IsUnsignedChar():
            return variant.ToUnsignedChar()
        elif variant.IsSignedChar():
            return variant.ToSignedChar()
        elif variant.IsShort():
            return variant.ToShort()
        elif variant.IsUnsignedShort():
            return variant.ToUnsignedShort()
        elif variant.IsInt():
            return variant.ToInt()
        elif variant.IsUnsignedInt():
            return variant.ToUnsignedInt()
        elif variant.IsLong():
            return variant.ToLong()
        elif variant.IsUnsignedLong():
            return variant.ToUnsignedLong()
        elif variant.IsLongLong():
            return variant.ToLongLong()
        elif variant.IsUnsignedLongLong():
            return variant.ToUnsignedLongLong()
        elif variant.IsVTKObject():
            return variant.ToVTKObject()
        else:
            raise NotImplementedError(
                f"Unsupported type for converting variant : {variant}"
            )
