# -----------------------------------------------------------------------------
# export PYTHONPATH=/home/seb/Documents/code/Async/build/lib/python3.10/site-packages
# export LD_LIBRARY_PATH=/home/seb/Documents/code/Async/build/lib
#
# python3.10 -m venv pv-venv
# source ./pv-venv/bin/activate
# pip install -U pip
# pip install trame
#
# python ./paraview/Remoting/Microservices/Testing/Trame/TestProxyDefinition.py
# -----------------------------------------------------------------------------

from parat.services import (
    ParaT,
    PipelineBuilder,
    DefinitionManager,
    ActiveObjects,
    PropertyManager,
    PipelineViewer,
)
from parat.trame.simput import ParaViewSimput

# to be replaced by DefinitionManager service API
from paraview.modules.vtkRemotingServerManagerCore import vtkProxyDefinitionManager
from paraview.modules.vtkRemotingServerManager import vtkSMProxySelectionModel

# -----------------------------------------------------------------------------

from trame.app import get_server, asynchronous
from trame.ui.vuetify import SinglePageWithDrawerLayout
from trame.widgets import vuetify, simput, trame, html

# -----------------------------------------------------------------------------

XML_GROUPS = {}


def data_info_to_str(data_info):
    lines = []
    lines.append(f"Points: {data_info.GetNumberOfPoints()}")
    lines.append(f"Cells: {data_info.GetNumberOfCells()}")
    lines.append(f"MTime: {data_info.GetMTime()}")
    lines.append(f"Bounds: {data_info.GetBounds()}")

    return "\n".join(lines)


def apply_exec(**kwargs):
    print("Apply")


class App:
    def __init__(self, server=None):
        if server is None:
            server = get_server()

        self.server = server
        self.state = server.state
        self.ctrl = server.controller

        # ParaView Async
        self._app = ParaT()
        self._pv_simput = ParaViewSimput(server, name="async")

        # internal state
        self.active_proxy = None
        self.active_representation = None
        self.active_view = None

        # state
        self.state.simput_active_source = 0
        self.state.xml_groups = []
        self.state.xml_names = []

        # controller
        self.ctrl.on_server_ready.add_task(self.initialize)
        self.ctrl.on_server_exited.add_task(self.finalize)
        self.ctrl.on_apply = apply_exec  # FIXME bug on trame-server
        self.ctrl.on_apply.add_task(self.on_update_data_info)
        self.ctrl.apply = self._pv_simput.apply
        self.ctrl.reset = self._pv_simput.reset

        # change
        self.state.change("xml_group")(self.on_group_change)

    async def initialize(self, **kwargs):
        self._session = await self._app.initialize()
        self._pipeline = PipelineViewer(self._session)
        self._builder = PipelineBuilder(self._session)
        self._def_mgt = DefinitionManager(self._session)
        self._active = ActiveObjects(self._session, "ActiveSources")
        self._prop_mgr = PropertyManager()

        # Bind definition service to simput helper
        self._pv_simput.set_definition_manager(self._def_mgt)

        # Tasks to monitor state change
        asynchronous.create_task(self.on_active_change())
        asynchronous.create_task(self.on_pipeline_change())

        # Update XML groups/names
        pdm = self._session.GetProxyDefinitionManager()
        all_definitions = pdm.GetDefinitions()
        iter = all_definitions.begin()
        end = all_definitions.end()
        while iter != end:
            xml_group = iter.GetGroup()
            xml_name = iter.GetName()
            XML_GROUPS.setdefault(xml_group, [])
            XML_GROUPS[xml_group].append(xml_name)
            iter.next()

        with self.state:
            self.state.xml_groups = list(XML_GROUPS.keys())
            self.state.xml_group = "sources"

        with self.state:
            self.state.xml_name = "ConeSource"

    async def finalize(self, **kwargs):
        await self._app.finalize()

    def on_group_change(self, xml_group, **kwargs):
        self.state.xml_names = XML_GROUPS[xml_group]
        self.state.xml_name = XML_GROUPS[xml_group][0]

    async def on_update_data_info(self):
        active_data_information = ""
        if self.active_proxy:
            await self._prop_mgr.UpdatePipeline(self.active_proxy)
            dataInformation = self.active_proxy.GetDataInformation(0)
            active_data_information = data_info_to_str(dataInformation)

        with self.state as state:
            self.state.active_data_information = active_data_information

    async def on_active_change(self):
        async for proxy in self._active.GetCurrentObservable():
            self.active_proxy = proxy
            active_ids = []
            active_id = "0"
            active_data_information = ""

            if proxy:
                active_ids = [str(proxy.GetGlobalID())]
                active_id = self._pv_simput.to_sinput(proxy).id
                await self._prop_mgr.UpdatePipeline(proxy)
                dataInformation = proxy.GetDataInformation(0)
                active_data_information = data_info_to_str(dataInformation)

            with self.state as state:
                state.git_tree_actives = active_ids
                self.state.simput_active_source = active_id
                self.state.active_data_information = active_data_information

    async def on_pipeline_change(self):
        async for pipelineState in self._pipeline.GetObservable():
            list_to_fill = []
            for item in pipelineState:
                node = {
                    "name": item.GetName(),
                    "parent": str(
                        item.GetParentIDs()[0] if len(item.GetParentIDs()) > 0 else 0
                    ),
                    "id": str(item.GetID()),
                    "visibile": 1,
                }
                list_to_fill.append(node)

            with self.state as state:
                state.git_tree_sources = list_to_fill

    def ui_active_change(self, active):
        proxy = self._session.GetProxyManager().FindProxy(int(active[0]))
        self._active.SetCurrentProxy(proxy, vtkSMProxySelectionModel.CLEAR)

    async def create_proxy(self):
        with self.state as state:
            xml_group = state.xml_group
            xml_name = state.xml_name
            proxy = None
            if xml_group == "sources":
                proxy = await self._builder.CreateSource(xml_group, xml_name)
            elif xml_group == "filters":
                input = self.active_proxy
                proxy = await self._builder.CreateFilter(
                    xml_group, xml_name, Input=input
                )
            elif xml_group == "representations":
                input = self.active_proxy
                view = self.active_view
                proxy = await self._builder.CreateRepresentation(
                    input, 0, view, xml_name
                )
                self.active_representation = proxy
            elif xml_group == "views":
                input = self.active_proxy
                proxy = await self._builder.CreateView(xml_group, xml_name)
                self.active_view = proxy
            else:
                print(f"Not sure what to create with {xml_group}::{xml_name}")

            # No proxy just skip work...
            if proxy is None:
                print("!!! No proxy created !!!")
                return

            # Load proxy definition
            simput_proxy = await self._pv_simput.create(proxy)

            if proxy == self.active_representation:
                state.simput_active_representation = simput_proxy.id


# -----------------------------------------------------------------------------
# Trame App
# -----------------------------------------------------------------------------

server = get_server()
app = App(server)

# -----------------------------------------------------------------------------
# GUI
# -----------------------------------------------------------------------------

SELECT_STYLE = dict(
    dense=True,
    hide_details=True,
    style="max-width: 200px",
    classes="mx-2",
)

BTN_STYLE = dict(
    classes="mx-2",
    small=True,
    outlined=True,
)

# -----------------------------------------------------------------------------

with SinglePageWithDrawerLayout(server) as layout:
    layout.root = app._pv_simput.root_widget

    layout.title.set_text("Parat")
    with layout.toolbar:
        vuetify.VSpacer()
        vuetify.VSelect(
            label="Group",
            v_model=("xml_group",),
            items=("xml_groups", []),
            **SELECT_STYLE,
        )
        vuetify.VSelect(
            label="Name",
            v_model=("xml_name",),
            items=("xml_names", []),
            **SELECT_STYLE,
        )
        with vuetify.VBtn(icon=True, outlined=True, small=True, click=app.create_proxy):
            vuetify.VIcon("mdi-plus")

    with layout.drawer as drawer:
        drawer.width = 300
        trame.GitTree(
            sources=("git_tree_sources", []),
            actives=("git_tree_actives", []),
            actives_change=(app.ui_active_change, "[$event]"),
        )
        vuetify.VDivider()
        html.Pre("{{ active_data_information }}")

    with layout.content:
        with vuetify.VContainer(fluid=True, classes="pa-0"):
            with vuetify.VCard(classes="ma-2", v_show="simput_active_source != '0'"):
                simput.SimputItem(item_id=("simput_active_source", None))
                vuetify.VDivider(classes="my-2")
                simput.SimputItem(item_id=("simput_active_representation", None))
                vuetify.VDivider(classes="my-2")
                with vuetify.VCardActions():
                    vuetify.VBtn(
                        "Reset",
                        **BTN_STYLE,
                        disabled=["!asyncChangeSet"],
                        click=app.ctrl.reset,
                    )
                    vuetify.VSpacer()
                    vuetify.VBtn(
                        "Apply ({{asyncChangeSet}})",
                        **BTN_STYLE,
                        disabled=["!asyncChangeSet"],
                        click=app.ctrl.apply,
                    )


if __name__ == "__main__":
    server.start()
