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

  Program:   ParaView
  Module:    vtkVolumeRepresentation.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 "vtkVolumeRepresentation.h"

#include "vtkColorTransferFunction.h"
#include "vtkDataSetAttributes.h"
#include "vtkInformation.h"
#include "vtkMath.h"
#include "vtkOutlineSource.h"
#include "vtkPVLODVolume.h"
#include "vtkProperty.h"
#include "vtkSMPTools.h"
#include "vtkVolumeProperty.h"

class vtkVolumeRepresentation::vtkRenderingInternals
{
public:
  // Rendering

  vtkSmartPointer<vtkVolumeProperty> Property;
  vtkSmartPointer<vtkPVLODVolume> Actor;
  vtkNew<vtkOutlineSource> OutlineSource;

  unsigned long DataSize = 0;
  double DataBounds[6];

  bool MapScalars = true;
  bool MultiComponentsMapping = false;

  bool UseSeparateOpacityArray = false;
  std::string OpacityArrayName;
  int OpacityArrayFieldAssociation = -1;
  int OpacityArrayComponent = -1;

  vtkRenderingInternals()
    : Property(vtk::TakeSmartPointer(vtkVolumeProperty::New()))
    , Actor(vtk::TakeSmartPointer(vtkPVLODVolume::New()))
  {
    this->Actor->SetProperty(this->Property);
    vtkMath::UninitializeBounds(this->DataBounds);
  }
};

//----------------------------------------------------------------------------
vtkVolumeRepresentation::vtkVolumeRepresentation()
{
  this->SetNumberOfInputPorts(1);
  this->SetNumberOfOutputPorts(0);
  this->SetNumberOfRenderingPorts(1);
}

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

//----------------------------------------------------------------------------
void vtkVolumeRepresentation::InitializeForDataProcessing()
{
  this->Superclass::InitializeForDataProcessing();
}

//----------------------------------------------------------------------------
void vtkVolumeRepresentation::InitializeForRendering()
{
  this->Superclass::InitializeForRendering();
  this->RInternals.reset(new vtkVolumeRepresentation::vtkRenderingInternals());
  this->RInternals->Actor->SetVisibility(this->GetVisibility());
}
//----------------------------------------------------------------------------
void vtkVolumeRepresentation::SetProperty(vtkVolumeProperty* property)
{
  if (this->RInternals)
  {
    auto& rinternals = (*this->RInternals);
    rinternals.Property = property;
    rinternals.Actor->SetProperty(property);
  }
}

//----------------------------------------------------------------------------
vtkVolumeProperty* vtkVolumeRepresentation::GetProperty() const
{
  if (this->RInternals)
  {
    auto& rinternals = (*this->RInternals);
    return rinternals.Property;
  }

  return nullptr;
}

//----------------------------------------------------------------------------
void vtkVolumeRepresentation::SetActor(vtkPVLODVolume* actor)
{
  if (this->RInternals)
  {
    auto& rinternals = (*this->RInternals);
    rinternals.Actor = actor;
    rinternals.Actor->SetProperty(rinternals.Property);
    assert(this->GetView() == nullptr);
  }
}

//----------------------------------------------------------------------------
vtkPVLODVolume* vtkVolumeRepresentation::GetActor() const
{
  if (this->RInternals)
  {
    auto& rinternals = (*this->RInternals);
    return rinternals.Actor;
  }

  return nullptr;
}

//----------------------------------------------------------------------------
void vtkVolumeRepresentation::SetVisibility(bool val)
{
  if (this->RInternals)
  {
    this->RInternals->Actor->SetVisibility(val);
  }
  this->Superclass::SetVisibility(val);
}

// Forwarded to vtkVolumeProperty.
//----------------------------------------------------------------------------
void vtkVolumeRepresentation::SetMapScalars(bool val)
{
  if (this->RInternals)
  {
    // the value is passed on to the vtkVolumeProperty in UpdateMapperParameters
    // since SetMapScalars and SetMultiComponentsMapping both control the same
    // vtkVolumeProperty ivar i.e. IndependentComponents.
    auto& rinternals = *this->RInternals;
    rinternals.MapScalars = val;
  }
}

//----------------------------------------------------------------------------
bool vtkVolumeRepresentation::GetMapScalars()
{
  if (this->RInternals)
  {
    return this->RInternals->MapScalars;
  }
  return false;
}

//----------------------------------------------------------------------------
void vtkVolumeRepresentation::SetMultiComponentsMapping(bool val)
{
  if (this->RInternals)
  {
    // the value is passed on to the vtkVolumeProperty in UpdateMapperParameters
    // since SetMapScalars and SetMultiComponentsMapping both control the same
    // vtkVolumeProperty ivar i.e. IndependentComponents.
    auto& rinternals = *this->RInternals;
    rinternals.MultiComponentsMapping = val;
  }
}

//----------------------------------------------------------------------------
bool vtkVolumeRepresentation::GetMultiComponentsMapping()
{
  if (this->RInternals)
  {
    return this->RInternals->MultiComponentsMapping;
  }
  return false;
}

//----------------------------------------------------------------------------
void vtkVolumeRepresentation::SetUseSeparateOpacityArray(bool value)
{
  if (this->RInternals)
  {
    auto& rinternals = *this->RInternals;
    if (rinternals.UseSeparateOpacityArray != value)
    {
      rinternals.UseSeparateOpacityArray = value;
    }
  }
}

//----------------------------------------------------------------------------
bool vtkVolumeRepresentation::GetUseSeparateOpacityArray()
{
  if (this->RInternals)
  {
    return this->RInternals->UseSeparateOpacityArray;
  }
  return false;
}

//----------------------------------------------------------------------------
void vtkVolumeRepresentation::SelectOpacityArray(
  int, int, int, int fieldAssociation, const char* name)
{
  if (this->RInternals)
  {
    auto& rinternals = *this->RInternals;
    std::string newName;
    if (name)
    {
      newName = std::string(name);
    }

    if (rinternals.OpacityArrayName != newName)
    {
      rinternals.OpacityArrayName = newName;
    }

    if (rinternals.OpacityArrayFieldAssociation != fieldAssociation)
    {
      rinternals.OpacityArrayFieldAssociation = fieldAssociation;
    }
  }
}

//----------------------------------------------------------------------------
void vtkVolumeRepresentation::SelectOpacityArrayComponent(int component)
{
  if (this->RInternals)
  {
    auto& rinternals = *this->RInternals;
    if (rinternals.OpacityArrayComponent != component)
    {
      rinternals.OpacityArrayComponent = component;
    }
  }
}

namespace
{

/**
 * Copies a component from one array to another. If the source component is >= the number of
 * components in the source array, the source array is treated as a vector and its magnitude
 * is computed and set in the destination component.
 */
void CopyComponent(vtkDataArray* dst, int dstComponent, vtkDataArray* src, int srcComponent)
{
  if (srcComponent < src->GetNumberOfComponents())
  {
    dst->CopyComponent(dstComponent, src, srcComponent);
  }
  else
  {
    vtkSMPTools::For(0, src->GetNumberOfTuples(), [&](vtkIdType start, vtkIdType end) {
      // Compute vector value
      auto inTuples = vtk::DataArrayTupleRange(src, start, end);
      auto outValues = vtk::DataArrayTupleRange(dst, start, end);
      auto outIter = outValues.begin();
      for (auto tuple : inTuples)
      {
        double magnitude = 0.0;
        for (auto component : tuple)
        {
          magnitude += static_cast<double>(component) * static_cast<double>(component);
        }
        (*outIter)[dstComponent] = std::sqrt(magnitude);
        ++outIter;
      }
    });
  }
}

} // end namespace

//----------------------------------------------------------------------------
bool vtkVolumeRepresentation::AppendOpacityComponent(vtkDataSet* dataset)
{
  if (!this->RInternals)
  {
    return false;
  }
  auto& internals = *(this->RInternals);
  // Get opacity array
  if (!dataset)
  {
    vtkErrorMacro("No input");
    return false;
  }

  vtkDataArray* opacityArray = dataset->GetAttributes(internals.OpacityArrayFieldAssociation)
                                 ->GetArray(internals.OpacityArrayName.c_str());
  if (!opacityArray)
  {
    vtkErrorMacro("No opacity array named '" << internals.OpacityArrayName << "' in input.");
    return false;
  }

  const char* colorArrayName = nullptr;
  int fieldAssociation = vtkDataObject::FIELD_ASSOCIATION_POINTS;
  vtkInformation* info = this->GetInputArrayInformation(0);
  if (info && info->Has(vtkDataObject::FIELD_ASSOCIATION()) &&
    info->Has(vtkDataObject::FIELD_NAME()))
  {
    colorArrayName = info->Get(vtkDataObject::FIELD_NAME());
    fieldAssociation = info->Get(vtkDataObject::FIELD_ASSOCIATION());

    if (colorArrayName)
    {
      vtkDataArray* colorArray = this->GetInputArrayToProcess(0, dataset);
      // Create a copy of the array
      std::string combinedName(colorArrayName);
      combinedName += "_and_opacity";
      vtkSmartPointer<vtkDataArray> combinedArray;
      combinedArray.TakeReference(colorArray->NewInstance());
      combinedArray->SetName(combinedName.c_str());
      int numComponents = colorArray->GetNumberOfComponents();
      if (numComponents != 1 && numComponents != 3)
      {
        vtkErrorMacro("Cannot use a separate opacity array name when color array has "
          << numComponents << " components.");
        return false;
      }
      // We will always create a two-component array, the first for color and the second for
      // opacity.
      combinedArray->SetNumberOfComponents(2);
      const int colorComponent = 0;
      const int opacityComponent = 1;
      combinedArray->SetNumberOfTuples(colorArray->GetNumberOfTuples());

      // Copy initial color component(s)
      vtkColorTransferFunction* ctf = internals.Property->GetRGBTransferFunction(0);
      int sourceComponent = ctf->GetVectorMode() == vtkScalarsToColors::COMPONENT
        ? ctf->GetVectorComponent()
        : colorArray->GetNumberOfComponents();

      // TODO - force update when component changes
      CopyComponent(combinedArray, colorComponent, colorArray, sourceComponent);

      // Copy the opacity component
      CopyComponent(combinedArray, opacityComponent, opacityArray, internals.OpacityArrayComponent);

      dataset->GetAttributes(fieldAssociation)->AddArray(combinedArray);
    }
  }

  return true;
}

//----------------------------------------------------------------------------
unsigned long vtkVolumeRepresentation::GetDataSize()
{
  assert(this->RInternals);
  return this->RInternals->DataSize;
}

//----------------------------------------------------------------------------
void vtkVolumeRepresentation::SetDataSize(unsigned long dataSize)
{
  assert(this->RInternals);
  this->RInternals->DataSize = dataSize;
}

//----------------------------------------------------------------------------
double* vtkVolumeRepresentation::GetDataBounds()
{
  assert(this->RInternals);
  return this->RInternals->DataBounds;
}

//----------------------------------------------------------------------------
void vtkVolumeRepresentation::SetDataBounds(double* dataBounds)
{
  assert(this->RInternals);
  std::copy(dataBounds, dataBounds + 6, this->RInternals->DataBounds);
}

//----------------------------------------------------------------------------
void vtkVolumeRepresentation::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);

  auto printObject = [&](vtkObject* object, const std::string& name) {
    if (object)
    {
      os << indent << name << ":\n";
      object->PrintSelf(os, indent.GetNextIndent());
    }
    else
    {
      os << indent << name << ": (none)\n";
    }
  };

  if (this->RInternals)
  {
    const auto& internals = *(this->RInternals);

    printObject(internals.Property, "Property");
    printObject(internals.Actor, "Actor");

    os << indent << "MapScalars: " << internals.MapScalars << endl;
    os << indent << "MultiComponentsMapping: " << internals.MultiComponentsMapping << endl;
    os << indent << "UseSeparateOpacityArray: " << internals.UseSeparateOpacityArray << endl;
    os << indent << "OpacityArrayName: " << internals.OpacityArrayName << endl;
    os << indent << "OpacityArrayFieldAssociation: " << internals.OpacityArrayFieldAssociation
       << endl;
    os << indent << "OpacityArrayComponent: " << internals.OpacityArrayComponent << endl;
  }
}
