// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
// .SECTION Thanks
// This test was written by Philippe Pebay, Kitware SAS 2012

#include "vtkSmartPointer.h"

#include "vtkAVSucdReader.h"
#include "vtkCamera.h"
#include "vtkCellData.h"
#include "vtkCompositeDataIterator.h"
#include "vtkDataSetMapper.h"
#include "vtkDoubleArray.h"
#include "vtkInformation.h"
#include "vtkMultiBlockDataSet.h"
#include "vtkNew.h"
#include "vtkProperty.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkRenderer.h"
#include "vtkTestUtilities.h"
#include "vtkThreshold.h"
#include "vtkUnstructuredGrid.h"
#include "vtkYoungsMaterialInterface.h"

//------------------------------------------------------------------------------
int TestYoungsMaterialInterface(int argc, char* argv[])
{
  // Create renderer and add actors to it
  vtkNew<vtkRenderer> renderer;
  renderer->SetBackground(.8, .8, .8);

  // Create render window;
  vtkNew<vtkRenderWindow> window;
  window->AddRenderer(renderer);
  window->SetSize(500, 200);
  window->SetMultiSamples(0);

  // Create interactor;
  vtkNew<vtkRenderWindowInteractor> interactor;
  interactor->SetRenderWindow(window);

  // Read from AVS UCD data in binary form;
  char* fileName = vtkTestUtilities::ExpandDataFileName(argc, argv, "Data/UCD2D/UCD_00005.inp");
  vtkNew<vtkAVSucdReader> reader;
  reader->SetFileName(fileName);
  delete[] fileName;

  // Update reader and get mesh cell data;
  reader->Update();
  vtkUnstructuredGrid* mesh = reader->GetOutput();
  vtkCellData* cellData = mesh->GetCellData();

  // Create normal vectors;
  cellData->SetActiveScalars("norme[0]");
  vtkDataArray* normX = cellData->GetScalars();
  cellData->SetActiveScalars("norme[1]");
  vtkDataArray* normY = cellData->GetScalars();
  vtkIdType n = normX->GetNumberOfTuples();
  vtkNew<vtkDoubleArray> norm;
  norm->SetNumberOfComponents(3);
  norm->SetNumberOfTuples(n);
  norm->SetName("norme");
  for (int i = 0; i < n; ++i)
  {
    norm->SetTuple3(i, normX->GetTuple1(i), normY->GetTuple1(i), 0.);
  }
  cellData->SetVectors(norm);

  // Extract submesh corresponding with cells containing material 2
  cellData->SetActiveScalars("Material Id");
  vtkNew<vtkThreshold> threshold2;
  threshold2->SetInputData(mesh);
  threshold2->SetInputArrayToProcess(
    0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, vtkDataSetAttributes::SCALARS);
  threshold2->SetThresholdFunction(vtkThreshold::THRESHOLD_LOWER);
  threshold2->SetLowerThreshold(2.0);
  threshold2->Update();
  vtkUnstructuredGrid* meshMat2 = threshold2->GetOutput();

  // Extract submesh corresponding with cells containing material 3
  vtkNew<vtkThreshold> threshold3;
  threshold3->SetInputData(mesh);
  threshold3->SetInputArrayToProcess(
    0, 0, 0, vtkDataObject::FIELD_ASSOCIATION_CELLS, vtkDataSetAttributes::SCALARS);
  threshold3->SetThresholdFunction(vtkThreshold::THRESHOLD_UPPER);
  threshold3->SetUpperThreshold(3.0);
  threshold3->Update();
  vtkUnstructuredGrid* meshMat3 = threshold3->GetOutput();

  // Make multiblock from extracted submeshes;
  vtkNew<vtkMultiBlockDataSet> meshMB;
  meshMB->SetNumberOfBlocks(2);
  meshMB->GetMetaData(static_cast<unsigned>(0))->Set(vtkCompositeDataSet::NAME(), "Material 2");
  meshMB->SetBlock(0, meshMat2);
  meshMB->GetMetaData(static_cast<unsigned>(1))->Set(vtkCompositeDataSet::NAME(), "Material 3");
  meshMB->SetBlock(1, meshMat3);

  // Create mapper for submesh corresponding to material 2;
  double* matRange = cellData->GetScalars()->GetRange();
  vtkNew<vtkDataSetMapper> meshMapper;
  meshMapper->SetInputData(meshMat2);
  meshMapper->SetScalarRange(matRange);
  meshMapper->SetScalarModeToUseCellData();
  meshMapper->SetColorModeToMapScalars();
  meshMapper->ScalarVisibilityOn();
  meshMapper->SetResolveCoincidentTopologyPolygonOffsetParameters(0, 1);
  meshMapper->SetResolveCoincidentTopologyToPolygonOffset();

  // Create wireframe actor for entire mesh
  vtkNew<vtkActor> meshActor;
  meshActor->SetMapper(meshMapper);
  meshActor->GetProperty()->SetRepresentationToWireframe();
  renderer->AddViewProp(meshActor);

  cellData->SetActiveScalars("frac_pres[1]");
  // Reconstruct Youngs material interface
  vtkNew<vtkYoungsMaterialInterface> youngs;
  youngs->SetInputData(meshMB);
  youngs->SetNumberOfMaterials(2);
  youngs->SetMaterialVolumeFractionArray(0, "frac_pres[1]");
  youngs->SetMaterialVolumeFractionArray(1, "frac_pres[2]");
  youngs->SetMaterialNormalArray(0, "norme");
  youngs->SetMaterialNormalArray(1, "norme");
  youngs->SetVolumeFractionRange(.001, .999);
  youngs->FillMaterialOn();
  youngs->RemoveAllMaterialBlockMappings();
  youngs->AddMaterialBlockMapping(-1);
  youngs->AddMaterialBlockMapping(1);
  youngs->AddMaterialBlockMapping(-2);
  youngs->AddMaterialBlockMapping(2);
  youngs->UseAllBlocksOff();
  youngs->Update();

  // Create mappers and actors for surface rendering of all reconstructed interfaces;
  vtkSmartPointer<vtkCompositeDataIterator> interfaceIterator;
  interfaceIterator.TakeReference(youngs->GetOutput()->NewIterator());
  interfaceIterator->SkipEmptyNodesOn();
  interfaceIterator->InitTraversal();
  interfaceIterator->GoToFirstItem();
  while (interfaceIterator->IsDoneWithTraversal() == 0)
  {
    // Select blue component of leaf mesh
    double bComp = interfaceIterator->GetCurrentFlatIndex() == 2 ? 0 : 1;

    // Fetch interface object and downcast to data set
    vtkDataObject* interfaceDO = interfaceIterator->GetCurrentDataObject();
    vtkDataSet* interface = vtkDataSet::SafeDownCast(interfaceDO);

    // Create mapper for interface
    vtkNew<vtkDataSetMapper> interfaceMapper;
    interfaceMapper->SetInputData(interface);
    interfaceIterator->GoToNextItem();
    interfaceMapper->ScalarVisibilityOff();
    interfaceMapper->SetResolveCoincidentTopologyPolygonOffsetParameters(1, 100);
    interfaceMapper->SetResolveCoincidentTopologyToPolygonOffset();

    // Create surface actor and add it to view
    vtkNew<vtkActor> interfaceActor;
    interfaceActor->SetMapper(interfaceMapper);
    interfaceActor->GetProperty()->SetColor(0., 1 - bComp, bComp);
    interfaceActor->GetProperty()->SetRepresentationToSurface();
    renderer->AddViewProp(interfaceActor);
  }

  // Render and test;
  window->Render();
  interactor->Start();

  return EXIT_SUCCESS;
}
