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

  Program:   Visualization Toolkit
  Module:    vtkPropertyAdapter.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm 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 "vtkPropertyAdapter.h"
#include "vtkAdapterUtilities.h"
#include "vtkCollection.h"
#include "vtkDomainAdapter.h"
#include "vtkLogger.h"
#include "vtkObjectFactory.h"
#include "vtkPVXMLElement.h"
#include "vtkPropertyGroupAdapter.h"
#include "vtkSMArrayListDomain.h"
#include "vtkSMArrayRangeDomain.h"
#include "vtkSMArraySelectionDomain.h"
#include "vtkSMBooleanDomain.h"
#include "vtkSMBoundsDomain.h"
#include "vtkSMCompositeTreeDomain.h"
#include "vtkSMDiscreteDoubleDomain.h"
#include "vtkSMDocumentation.h"
#include "vtkSMDomainIterator.h"
#include "vtkSMDoubleRangeDomain.h"
#include "vtkSMDoubleVectorProperty.h"
#include "vtkSMEnumerationDomain.h"
#include "vtkSMFileListDomain.h"
#include "vtkSMInputProperty.h"
#include "vtkSMIntRangeDomain.h"
#include "vtkSMIntVectorProperty.h"
#include "vtkSMProperty.h"
#include "vtkSMProxyListDomain.h"
#include "vtkSMProxyProperty.h"
#include "vtkSMStringListDomain.h"
#include "vtkSMStringVectorProperty.h"
#include "vtkSMVectorProperty.h"
#include "vtkSmartPointer.h"

#include <vtksys/SystemTools.hxx>

namespace
{
template <typename PropertyType>
vtkWidgetAdapter GetWidget(PropertyType* smproperty);

template <>
vtkWidgetAdapter GetWidget(vtkSMDoubleVectorProperty* dvp)
{
  vtkWidgetAdapter result;
  auto* range = dvp->FindDomain<vtkSMDoubleRangeDomain>();
  if (dvp->GetRepeatable())
  {
    // use `pqScalarValueListPropertyWidget` since property is repeatable
    result.PanelWidget = "pqScalarValueListPropertyWidget";
  }
  else if (range)
  {
    if (dvp->GetNumberOfElements() == 1 &&
      ((range->GetMinimumExists(0) && range->GetMaximumExists(0)) ||
        (dvp->FindDomain<vtkSMArrayRangeDomain>() != nullptr ||
          dvp->FindDomain<vtkSMBoundsDomain>() != nullptr)))
    {
      // bounded ranges are represented with a slider and a spin box
      // use `pqDoubleRangeWidget` since property has range with min and max
      result.PanelWidget = "pqDoubleRangeWidget";
    }
    else
    {
      // unbounded ranges are represented with a line edit

      if (dvp->GetNumberOfElements() == 6)
      {
        // "use 6 `pqDoubleLineEdit` in a 3X2 grid.
        result.PanelWidget = "pqDoubleLineEdit";
        result.Layout = "l3";
      }
      else
      {
        // use dvp->GetNumberOfElements()  `pqDoubleLineEdit` instance(s)
        result.PanelWidget = "pqDoubleLineEdit";
        if (dvp->GetNumberOfElements() > 1)
        {
          result.Layout = "l" + std::to_string(dvp->GetNumberOfElements());
        }
      }
    }
  } // else if (range)
  else if (auto discrete = dvp->FindDomain<vtkSMDiscreteDoubleDomain>())
  {
    if (discrete->GetValuesExists())
    {
      // use discrete slider widget `pqDiscreteDoubleWidget`
      result.PanelWidget = "pqDiscreteDoubleWidget";
    }
    else
    {
      vtkLogF(ERROR, "vtkSMDiscreteDoubleDomain does not contain any value.");
    }
  }
  return result;
  // TODO missing
  // - componentLabels
  // - scale by button
}
template <>
vtkWidgetAdapter GetWidget(vtkSMIntVectorProperty* ivp)
{
  vtkWidgetAdapter result;
  if (auto* domain = ivp->FindDomain<vtkSMBooleanDomain>())
  {
    // use checkbox for boolean property."
    result.PanelWidget = "QCheckBox";
  }
  else if (auto* domain = ivp->FindDomain<vtkSMEnumerationDomain>())
  {
    if (vtkSMVectorProperty::SafeDownCast(ivp)->GetRepeatCommand())
    {
      // use a multi-select enumerated list (in a `pqTreeWidget`)
      result.PanelWidget = "pqTreeWidget";
    }
    else
    {
      // use a combo-box for a enumerated list.
      result.PanelWidget = "QComboBox";
    }
  }
  else if (auto* domain = ivp->FindDomain<vtkSMCompositeTreeDomain>())
  {
    // Should have been handled by pqDataAssemblyPropertyWidget.
    vtkLogF(ERROR, "Should have been handled by pqDataAssemblyPropertyWidget");
  }
  else if (auto* range = ivp->FindDomain<vtkSMIntRangeDomain>())
  {
    if (ivp->GetRepeatable())
    {
      // use `pqScalarValueListPropertyWidget` for repeatable property.
      result.PanelWidget = "pqScalarValueListPropertyWidget";
    }
    else if (ivp->GetNumberOfElements() == 1 && range->GetMinimumExists(0) &&
      range->GetMaximumExists(0))
    {
      // use  a slider and a spin box
      result.PanelWidget = "pqIntRangeWidget";
    }
    else
    {
      // unbounded ranges are represented with a line edit

      if (ivp->GetNumberOfElements() == 6)
      {
        // "use 6 `pqLineEdit`s in a 3X2 grid.
        result.PanelWidget = "pqLineEdit";
        result.Layout = "l3";
      }
      else
      {
        // use ivp->GetNumberOfElements()  `pqLineEdit` instance(s)
        result.PanelWidget = "pqLineEdit";
        if (ivp->GetNumberOfElements() > 1)
        {
          result.Layout = "l" + std::to_string(ivp->GetNumberOfElements());
        }
      }
    }
  }

  return result;
}

template <>
vtkWidgetAdapter GetWidget(vtkSMStringVectorProperty* svp)
{
  vtkWidgetAdapter result;
  // pqArraySelectorPropertyWidget
  //
  // pqArraySelectorPropertyWidget is intended to be used for
  // vtkSMStringVectorProperty instances that have a vtkSMArrayListDomain domain
  // and want to show a single combo-box to allow the user to choose the array to use.
  //
  // We support non-repeatable string-vector property with a 1, 2, or 5 elements.
  // When 1 element is present, we interpret the property value as the name of
  // chosen array, thus the user won't be able to pick array association. While
  // for 2 and 5 element properties the array association and name can be picked.
  //
  // The list of available arrays is built using the vtkSMArrayListDomain and
  // updated anytime the domain is updated. If the currently chosen value is no
  // longer in the domain, we will preserve it and flag it by adding a `(?)`
  // suffix to the displayed label.
  //
  // `pqStringVectorPropertyWidget::createWidget` instantiates this for
  // any string vector property with a vtkSMArrayListDomain that is not
  // repeatable.
  //
  if (svp && svp->FindDomain<vtkSMArrayListDomain>() && !svp->GetRepeatable())
  {
    result.PanelWidget = "QComboBox";
  }

  if (!result.PanelWidget.empty())
  {
    return result;
  }

  // pqStringVectorPropertyWidget
  vtkPVXMLElement* hints = svp->GetHints();

  bool multiline_text = false;
  bool python = false; //  python highlighting
  bool showLabel = false;
  // std::string placeholderText;
  if (hints)
  {
    vtkPVXMLElement* widgetHint = hints->FindNestedElementByName("Widget");
    if (widgetHint && widgetHint->GetAttribute("type") &&
      strcmp(widgetHint->GetAttribute("type"), "multi_line") == 0)
    {
      multiline_text = true;
    }
    if (widgetHint && widgetHint->GetAttribute("syntax") &&
      strcmp(widgetHint->GetAttribute("syntax"), "python") == 0)
    {
      python = true;
    }
    // if (vtkPVXMLElement* phtElement = hints->FindNestedElementByName("PlaceholderText"))
    //{
    //  placeholderText = phtElement->GetCharacterData();
    //  placeholderText = placeholderText.trimmed();
    //}
    // showLabel = (hints->FindNestedElementByName("ShowLabel") != nullptr);
  }

  if (auto* fileListDomain = svp->FindDomain<vtkSMFileListDomain>())
  {
    result.PanelWidget = "pqFileChooserWidget";
  }
  else if (auto* arrayListDomain = svp->FindDomain<vtkSMArrayListDomain>())
  {
    result.PanelWidget = "pqArraySelectionWidget";
  }
  else if (auto* arraySelectionDomain = svp->FindDomain<vtkSMArraySelectionDomain>())
  {
    result.PanelWidget = "pqArraySelectionWidget";
  }
  else if (auto* stringListDomain = svp->FindDomain<vtkSMStringListDomain>())
  {
    result.PanelWidget = "QComboBox";
  }
  else if (multiline_text)
  {
    result.PanelWidget = "pqTextEdit";
  }
  else if (auto* enumerationDomain = svp->FindDomain<vtkSMEnumerationDomain>())
  {
    result.PanelWidget = "QComboBox";
  }
  else
  {
    if (svp->GetRepeatable())
    {
      result.PanelWidget = "pqScalarValueListPropertyWidget";
    }
    else
    {
      result.PanelWidget = "pqLineEdit";
    }
  }

  return result;
}

template <>
vtkWidgetAdapter GetWidget(vtkSMProxyProperty* pp)
{
  vtkWidgetAdapter result;
  bool selection_input =
    (pp->GetHints() && pp->GetHints()->FindNestedElementByName("SelectionInput"));

  if (selection_input)
  {
    result.PanelWidget = "pqSelectionInputWidget";
  }
  else if (auto* domain = pp->FindDomain<vtkSMProxyListDomain>())
  {
    result.PanelWidget = "pqProxySelectionWidget";
  }

  return result;
}

}

//-----------------------------------------------------------------------------
vtkStandardNewMacro(vtkPropertyAdapter);

//-----------------------------------------------------------------------------
vtkPropertyAdapter::vtkPropertyAdapter() = default;

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

//-----------------------------------------------------------------------------
void vtkPropertyAdapter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "SMProperty " << this->SMProperty;
  this->SMProperty->PrintSelf(os, indent.GetNextIndent());
}

//-----------------------------------------------------------------------------
void vtkPropertyAdapter::SetSMProperty(vtkSMProperty* property)
{
  this->SMProperty = property;
  vtkSMDomainIterator* iterator = property->NewDomainIterator();
  for (iterator->Begin(); !iterator->IsAtEnd(); iterator->Next())
  {
    auto adapter = vtk::TakeSmartPointer(vtkDomainAdapter::New());
    adapter->SetSMDomain(iterator->GetDomain());
    this->Domains.push_back(adapter);
  }
  iterator->Delete();
  this->PopulateWidget();
}

//-----------------------------------------------------------------------------
void vtkPropertyAdapter::SetPropertyGroup(vtkSmartPointer<vtkPropertyGroupAdapter> group)
{
  this->Group = group;
}

//-----------------------------------------------------------------------------
vtkSmartPointer<vtkPropertyGroupAdapter> vtkPropertyAdapter::GetPropertyGroup() const
{
  return this->Group;
}

//-----------------------------------------------------------------------------
void vtkPropertyAdapter::PopulateWidget()
{
  // TODO handle special widgets
  if (const char* widget = this->SMProperty->GetPanelWidget())
  {
    this->Widget.PanelWidget = widget;
  }
  else if (auto* dvp = vtkSMDoubleVectorProperty::SafeDownCast(this->SMProperty))
  {
    this->Widget = ::GetWidget(dvp);
  }
  else if (auto* ivp = vtkSMIntVectorProperty::SafeDownCast(this->SMProperty))
  {
    this->Widget = ::GetWidget(ivp);
  }
  else if (auto* svp = vtkSMStringVectorProperty::SafeDownCast(this->SMProperty))
  {
    this->Widget = ::GetWidget(svp);
  }
  else if (auto* pp = vtkSMProxyProperty::SafeDownCast(this->SMProperty))
  {
    this->Widget = ::GetWidget(pp);
  }
  // else if (smproperty && strcmp(smproperty->GetClassName(), "vtkSMProperty") == 0)
  //{
  //  widget = new pqCommandPropertyWidget(smproperty, smproxy, parentObj);
  //}
  else
  {
    vtkLogF(ERROR, "Unsupported property type %s", this->SMProperty->GetClassName());
  }
}

//------------------------------------------------------------------------------
std::string vtkPropertyAdapter::GetName() const
{
  return this->SMProperty->GetXMLName();
}

//------------------------------------------------------------------------------
std::string vtkPropertyAdapter::GetLabel() const
{
  return this->SMProperty->GetXMLLabel();
}

//------------------------------------------------------------------------------
vtkSMDocumentation* vtkPropertyAdapter::GetDocumentation() const
{
  return this->SMProperty->GetDocumentation();
}

//------------------------------------------------------------------------------
std::string vtkPropertyAdapter::GetDocumentationDescriptionAsString() const
{
  std::string result;
  if (vtkSMDocumentation* doc = this->GetDocumentation())
  {
    if (const char* description = doc->GetDescription())
    {
      result.assign(description);
      vtksys::SystemTools::ReplaceString(result, "\n", " ");
      const std::string doubleSpace = "  ";
      do
      {
        vtksys::SystemTools::ReplaceString(result, doubleSpace, " ");
      } while (result.find(doubleSpace) != std::string::npos);
    }
  }
  return result;
}

//------------------------------------------------------------------------------
bool vtkPropertyAdapter::GetInformationOnly() const
{
  return this->SMProperty->GetInformationOnly();
}

//------------------------------------------------------------------------------
int vtkPropertyAdapter::GetType() const
{
  return this->SMProperty->GetElementType();
}

//------------------------------------------------------------------------------
int vtkPropertyAdapter::GetSize() const
{
  if (vtkSMVectorProperty* vectorProperty = vtkSMVectorProperty::SafeDownCast(this->SMProperty))
  {
    return vectorProperty->GetNumberOfElements();
  }
  else if (vtkSMInputProperty* inputProperty = vtkSMInputProperty::SafeDownCast(this->SMProperty))
  {
    if (inputProperty->GetMultipleInput())
    {
      return -1;
    }
    else
    {
      return 1;
    }
  }

  return -1;
}

//------------------------------------------------------------------------------
bool vtkPropertyAdapter::GetIsInternal() const
{
  return this->SMProperty->GetIsInternal();
}

//------------------------------------------------------------------------------
bool vtkPropertyAdapter::GetIsRepeatable() const
{
  return this->SMProperty->GetRepeatable();
}

//------------------------------------------------------------------------------
std::string vtkPropertyAdapter::GetPropertyClassName() const
{
  return this->SMProperty->GetClassName();
}

//------------------------------------------------------------------------------
vtkWidgetAdapter vtkPropertyAdapter::GetPanelWidget() const
{
  return this->Widget;
}

//------------------------------------------------------------------------------
std::string vtkPropertyAdapter::GetPanelVisibility() const
{
  return this->SMProperty->GetPanelVisibility();
}

//------------------------------------------------------------------------------
vtkSmartPointer<vtkPVXMLElement> vtkPropertyAdapter::GetHints() const
{
  return this->SMProperty->GetHints();
}

//------------------------------------------------------------------------------
std::vector<vtkSmartPointer<vtkDomainAdapter>> vtkPropertyAdapter::GetDomains() const
{
  return this->Domains;
}

//------------------------------------------------------------------------------
std::vector<std::string> vtkPropertyAdapter::GetDecorators() const
{
  return vtkAdapterUtilities::GetDecoratorNames(this->SMProperty->GetHints());
}
