/*=========================================================================

  Program:   ParaView
  Module:    vtkPTSDefinitionManager.cxx

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.paraview.org/HTML/Copyright.html for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notice for more information.

=========================================================================*/
#include "vtkPTSDefinitionManager.h"

#include "vtkClientSession.h"
#include "vtkCollection.h"
#include "vtkLayoutGenerator.h"
#include "vtkObjectFactory.h"
#include "vtkPropertyWidgetDecorator.h"
#include "vtkProxyAdapter.h"
#include "vtkRemotingCoreUtilities.h"
#include "vtkSMProperty.h"
#include "vtkSMPropertyGroup.h"
#include "vtkSMProxy.h"
#include "vtkSMSessionProxyManager.h"

#include <exception> // for std::runtime_error
class vtkPTSDefinitionManager::vtkInternals
{
public:
  const std::thread::id OwnerTID{ std::this_thread::get_id() };

  vtkSmartPointer<vtkClientSession> Session;
};

vtkStandardNewMacro(vtkPTSDefinitionManager);
//-----------------------------------------------------------------------------
vtkPTSDefinitionManager::vtkPTSDefinitionManager()
  : Internals(new vtkPTSDefinitionManager::vtkInternals())
{
}

//-----------------------------------------------------------------------------
vtkPTSDefinitionManager::~vtkPTSDefinitionManager() = default;

//-----------------------------------------------------------------------------
void vtkPTSDefinitionManager::SetSession(vtkClientSession* session)
{
  auto& internals = (*this->Internals);
  vtkRemotingCoreUtilities::EnsureThread(internals.OwnerTID);
  if (internals.Session != session)
  {
    internals.Session = session;
    //@TODO  push new definitions
  }
}

//-----------------------------------------------------------------------------
vtkClientSession* vtkPTSDefinitionManager::GetSession() const
{
  const auto& internals = (*this->Internals);
  vtkRemotingCoreUtilities::EnsureThread(internals.OwnerTID);
  return internals.Session;
}

//-----------------------------------------------------------------------------
rxcpp::observable<vtkSmartPointer<vtkProxyAdapter>> vtkPTSDefinitionManager::GetDefinition(
  vtkSMProxy* smproxy) const
{
  vtkRemotingCoreUtilities::EnsureThread(this->Internals->OwnerTID);
  auto& internals = (*this->Internals);
  if (internals.Session == nullptr)
  {
    return rxcpp::observable<>::error<vtkSmartPointer<vtkProxyAdapter>>(
      std::runtime_error("Session was not set!"));
  }
  vtkSmartPointer<vtkProxyAdapter> proxyAdapter = vtk::TakeSmartPointer(vtkProxyAdapter::New());
  proxyAdapter->SetSMProxy(smproxy);

  return rxcpp::observable<>::just(proxyAdapter);
}

//-----------------------------------------------------------------------------
rxcpp::observable<vtkSmartPointer<vtkProxyAdapter>> vtkPTSDefinitionManager::GetDefinition(
  const std::string& groupName, const std::string& proxyName) const
{
  vtkRemotingCoreUtilities::EnsureThread(this->Internals->OwnerTID);
  auto& internals = (*this->Internals);
  if (internals.Session == nullptr)
  {
    return rxcpp::observable<>::error<vtkSmartPointer<vtkProxyAdapter>>(
      std::runtime_error("Session was not set!"));
  }
  vtkSMProxy* smproxy =
    internals.Session->GetProxyManager()->GetPrototypeProxy(groupName.c_str(), proxyName.c_str());
  vtkSmartPointer<vtkProxyAdapter> proxyAdapter = vtk::TakeSmartPointer(vtkProxyAdapter::New());
  proxyAdapter->SetSMProxy(smproxy);

  return rxcpp::observable<>::just(proxyAdapter);

  return this->GetDefinition(smproxy);
}

//-----------------------------------------------------------------------------
rxcpp::observable<vtkSmartPointer<vtkPVXMLElement>> vtkPTSDefinitionManager::GetLayout(
  vtkSMProxy* smproxy) const
{
  vtkRemotingCoreUtilities::EnsureThread(this->Internals->OwnerTID);
  auto& internals = (*this->Internals);
  if (internals.Session == nullptr)
  {
    return rxcpp::observable<>::error<vtkSmartPointer<vtkPVXMLElement>>(
      std::runtime_error("Session was not set!"));
  }
  vtkSmartPointer<vtkPVXMLElement> xml = vtkLayoutGenerator::GetLayout(smproxy);

  return rxcpp::observable<>::just(xml);
}

//-----------------------------------------------------------------------------
rxcpp::observable<vtkSmartPointer<vtkPVXMLElement>> vtkPTSDefinitionManager::GetLayout(
  const std::string& groupName, const std::string& proxyName) const
{
  vtkRemotingCoreUtilities::EnsureThread(this->Internals->OwnerTID);
  auto& internals = (*this->Internals);
  if (internals.Session == nullptr)
  {
    return rxcpp::observable<>::error<vtkSmartPointer<vtkPVXMLElement>>(
      std::runtime_error("Session was not set!"));
  }
  vtkSMProxy* smproxy =
    internals.Session->GetProxyManager()->GetPrototypeProxy(groupName.c_str(), proxyName.c_str());

  return this->GetLayout(smproxy);
}
//-----------------------------------------------------------------------------
std::vector<vtkSmartPointer<vtkPropertyWidgetDecorator>>
vtkPTSDefinitionManager::GetPropertyDecorators(
  vtkSMProxy* proxy, const std::string& propertyName) const
{
  if (proxy == nullptr)
  {
    vtkLogF(ERROR, " proxy is nullptr !");
    return {};
  }
  vtkSMProperty* property = proxy->GetProperty(propertyName.c_str());

  if (property == nullptr)
  {
    vtkLogF(ERROR, "could not locaty property '%s' in proxy '%s'", proxy->GetXMLName(),
      propertyName.c_str());
    return {};
  }

  std::vector<vtkSmartPointer<vtkPropertyWidgetDecorator>> result;

  if (auto* hints = property->GetHints())
  {
    vtkNew<vtkCollection> collection;
    hints->FindNestedElementByName("PropertyWidgetDecorator", collection.GetPointer());
    for (int cc = 0; cc < collection->GetNumberOfItems(); cc++)
    {
      vtkPVXMLElement* elem = vtkPVXMLElement::SafeDownCast(collection->GetItemAsObject(cc));
      if (elem && elem->GetAttribute("type"))
      {
        result.emplace_back(vtkPropertyWidgetDecorator::create(elem, proxy));
      }
    }
  }
  return result;
}

//-----------------------------------------------------------------------------
std::vector<vtkSmartPointer<vtkPropertyWidgetDecorator>>
vtkPTSDefinitionManager::GetPropertyGroupDecorators(
  vtkSMProxy* proxy, const std::string& propertyGroupName) const
{
  if (proxy == nullptr)
  {
    vtkLogF(ERROR, " proxy is nullptr !");
    return {};
  }
  size_t n = proxy->GetNumberOfPropertyGroups();
  vtkSMPropertyGroup* group = nullptr;
  for (size_t i = 0; i < proxy->GetNumberOfPropertyGroups(); i++)
  {
    if (propertyGroupName == proxy->GetPropertyGroup(i)->GetName())
    {
      group = proxy->GetPropertyGroup(i);
      break;
    }
  }

  if (group == nullptr)
  {
    vtkLogF(ERROR, "could not locate group '%s' in proxy '%s'", proxy->GetXMLName(),
      propertyGroupName.c_str());
    return {};
  }

  std::vector<vtkSmartPointer<vtkPropertyWidgetDecorator>> result;

  if (auto* hints = group->GetHints())
  {
    vtkNew<vtkCollection> collection;
    hints->FindNestedElementByName("PropertyWidgetDecorator", collection.GetPointer());
    for (int cc = 0; cc < collection->GetNumberOfItems(); cc++)
    {
      vtkPVXMLElement* elem = vtkPVXMLElement::SafeDownCast(collection->GetItemAsObject(cc));
      if (elem && elem->GetAttribute("type"))
      {
        result.emplace_back(vtkPropertyWidgetDecorator::create(elem, proxy));
      }
    }
  }
  return result;
}
//-----------------------------------------------------------------------------
void vtkPTSDefinitionManager::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  const auto& internals = (*this->Internals);
  os << indent << "Session: " << internals.Session << "\n";
}
