// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
// Test the vtkImageAlgorithm::CopyAttributeData() method, which copies
// all of the attribute data arrays (PointData and CellData) that is
// not usually handled by the Execute methods of the imaging filters
// (Execute methods typically process only the PointData Scalars).

#include "vtkCellData.h"
#include "vtkFloatArray.h"
#include "vtkImageAlgorithm.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkIntArray.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkSmartPointer.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include "vtkStringArray.h"
#include "vtkVariant.h"

#include <cmath>
#include <cstring>

// Make a dummy image filter that does nothing but call CopyAttributeData.
class vtkDummyImageFilter : public vtkImageAlgorithm
{
public:
  static vtkDummyImageFilter* New();
  vtkTypeMacro(vtkDummyImageFilter, vtkImageAlgorithm);

protected:
  vtkDummyImageFilter() = default;
  ~vtkDummyImageFilter() override = default;

  int RequestData(vtkInformation* request, vtkInformationVector** inputVector,
    vtkInformationVector* outputVector) override;

private:
  vtkDummyImageFilter(const vtkDummyImageFilter&) = delete;
  void operator=(const vtkDummyImageFilter&) = delete;
};

vtkStandardNewMacro(vtkDummyImageFilter);

int vtkDummyImageFilter::RequestData(
  vtkInformation*, vtkInformationVector** inputVector, vtkInformationVector* outputVector)
{
  vtkInformation* inInfo = inputVector[0]->GetInformationObject(0);
  vtkImageData* inData = vtkImageData::SafeDownCast(inInfo->Get(vtkDataObject::DATA_OBJECT()));

  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  vtkImageData* outData = vtkImageData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  int extent[6];
  outInfo->Get(vtkStreamingDemandDrivenPipeline::UPDATE_EXTENT(), extent);
  this->AllocateOutputData(outData, outInfo, extent);

  // this is what we are testing
  this->CopyAttributeData(inData, outData, inputVector);

  // scalars would usually be processed here, but this is a dummy filter

  return 1;
}

int TestCopyAttributeData(int, char*[])
{
  int extent[6] = { 0, 6, 0, 4, 0, 2 };
  int outExt[6] = { 0, 4, 2, 2, 0, 2 };

  vtkSmartPointer<vtkImageData> image = vtkSmartPointer<vtkImageData>::New();
  image->SetExtent(extent);
  image->AllocateScalars(VTK_UNSIGNED_CHAR, 1);

  vtkIdType numPoints = image->GetNumberOfPoints();
  vtkIdType numCells = image->GetNumberOfCells();

  vtkSmartPointer<vtkFloatArray> pointVectors = vtkSmartPointer<vtkFloatArray>::New();
  pointVectors->SetName("ImageVectors");
  pointVectors->SetNumberOfComponents(3);
  pointVectors->SetNumberOfTuples(numPoints);

  for (vtkIdType i = 0; i < numPoints; i++)
  {
    double v[3];
    v[0] = sin(i * 0.5);
    v[1] = cos(i * 0.5);
    v[2] = sin(i * 0.1);
    pointVectors->SetTuple(i, v);
  }

  vtkSmartPointer<vtkIntArray> cellScalars = vtkSmartPointer<vtkIntArray>::New();
  cellScalars->SetName("CellScalars");
  cellScalars->SetNumberOfValues(numCells);

  vtkSmartPointer<vtkStringArray> cellStrings = vtkSmartPointer<vtkStringArray>::New();
  cellStrings->SetName("CellStrings");
  cellStrings->SetNumberOfValues(numCells);

  for (vtkIdType j = 0; j < numCells; j++)
  {
    vtkVariant val(j);
    cellScalars->SetValue(j, j);
    cellStrings->SetValue(j, val.ToString());
  }

  image->GetPointData()->SetVectors(pointVectors);
  image->GetCellData()->SetScalars(cellScalars);
  image->GetCellData()->AddArray(cellStrings);

  vtkSmartPointer<vtkDummyImageFilter> filter = vtkSmartPointer<vtkDummyImageFilter>::New();

  filter->SetInputData(image);

  for (int r = 0; r < 2; r++)
  {
    filter->UpdateExtent(outExt);

    vtkImageData* output = filter->GetOutput();

    vtkDataArray* outPointVectors = output->GetPointData()->GetVectors();
    vtkDataArray* outCellScalars = output->GetCellData()->GetScalars();
    vtkStringArray* outCellStrings =
      vtkArrayDownCast<vtkStringArray>(output->GetCellData()->GetAbstractArray("CellStrings"));

    for (int zId = outExt[4]; zId <= outExt[5]; zId++)
    {
      for (int yId = outExt[2]; yId <= outExt[3]; yId++)
      {
        for (int xId = outExt[0]; xId <= outExt[1]; xId++)
        {
          vtkIdType inIdx = (zId - extent[4]) * (extent[3] - extent[2] + 1);
          inIdx = (inIdx + yId - extent[2]) * (extent[1] - extent[0] + 1);
          inIdx = inIdx + xId - extent[0];
          vtkIdType outIdx = (zId - outExt[4]) * (outExt[3] - outExt[2] + 1);
          outIdx = (outIdx + yId - outExt[2]) * (outExt[1] - outExt[0] + 1);
          outIdx = outIdx + xId - outExt[0];
          double v1[3], v2[3];
          pointVectors->GetTuple(inIdx, v1);
          outPointVectors->GetTuple(outIdx, v2);
          if (v1[0] != v2[0] || v1[1] != v2[1] || v1[2] != v2[2])
          {
            cerr << "point attribute value mismatch ";
            return 1;
          }
        }
      }
    }

    for (int zId = outExt[4]; zId < outExt[5]; zId++)
    {
      int ye = (outExt[2] == outExt[3]);
      for (int yId = outExt[2]; yId < outExt[3] + ye; yId++)
      {
        for (int xId = outExt[0]; xId < outExt[1]; xId++)
        {
          vtkIdType inIdx = (zId - extent[4]) * (extent[3] - extent[2]);
          inIdx = (inIdx + yId - extent[2]) * (extent[1] - extent[0]);
          inIdx = inIdx + xId - extent[0];
          vtkIdType outIdx = (zId - outExt[4]) * (outExt[3] - outExt[2] + ye);
          outIdx = (outIdx + yId - outExt[2]) * (outExt[1] - outExt[0]);
          outIdx = outIdx + xId - outExt[0];
          double s1, s2;
          cellScalars->GetTuple(inIdx, &s1);
          outCellScalars->GetTuple(outIdx, &s2);
          if (s1 != s2)
          {
            cerr << "cell attribute value mismatch\n";
            return 1;
          }
          if (cellStrings->GetValue(inIdx) != outCellStrings->GetValue(outIdx))
          {
            cerr << "cell attribute string mismatch\n";
            return 1;
          }
        }
      }
    }

    // try again with full extent to test pass data
    outExt[0] = extent[0];
    outExt[1] = extent[1];
    outExt[2] = extent[2];
    outExt[3] = extent[3];
    outExt[4] = extent[4];
    outExt[5] = extent[5];
  }

  return 0;
}
