// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
// SPDX-License-Identifier: BSD-3-Clause
#include "vtkXMLWriterC.h"

#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkDataArray.h"
#include "vtkDataSet.h"
#include "vtkDataSetAttributes.h"
#include "vtkImageData.h"
#include "vtkPointData.h"
#include "vtkPointSet.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkRectilinearGrid.h"
#include "vtkSmartPointer.h"
#include "vtkStructuredGrid.h"
#include "vtkUnstructuredGrid.h"
#include "vtkXMLImageDataWriter.h"
#include "vtkXMLPolyDataWriter.h"
#include "vtkXMLRectilinearGridWriter.h"
#include "vtkXMLStructuredGridWriter.h"
#include "vtkXMLUnstructuredGridWriter.h"

// Function to allocate a vtkDataArray and point it at the given data.
// The data are not copied.
static vtkSmartPointer<vtkDataArray> vtkXMLWriterC_NewDataArray(const char* method,
  const char* name, int dataType, void* data, vtkIdType numTuples, int numComponents);

// Function to allocate a vtkCellArray and point it at the given
// cells.  The cells are not copied.
static vtkSmartPointer<vtkCellArray> vtkXMLWriterC_NewCellArray(
  const char* method, vtkIdType ncells, vtkIdType* cells, vtkIdType cellsSize);

// Function to implement vtkXMLWriterC_SetPointData and
// vtkXMLWriterC_SetCellData without duplicate code.
static void vtkXMLWriterC_SetDataInternal(vtkXMLWriterC* self, const char* name, int dataType,
  void* data, vtkIdType numTuples, int numComponents, const char* role, const char* method,
  int isPoints);

extern "C"
{

  //----------------------------------------------------------------------------
  // Define the interface structure.  Note this is a C struct so it has no
  // real constructor or destructor.
  struct vtkXMLWriterC_s
  {
    vtkSmartPointer<vtkXMLWriter> Writer;
    vtkSmartPointer<vtkDataObject> DataObject;
    int Writing;
  };

  //----------------------------------------------------------------------------
  vtkXMLWriterC* vtkXMLWriterC_New()
  {
    if (vtkXMLWriterC* self = new vtkXMLWriterC)
    {
      // Initialize the object.
      self->Writer = nullptr;
      self->DataObject = nullptr;
      self->Writing = 0;
      return self;
    }
    else
    {
      vtkGenericWarningMacro("Failed to allocate a vtkXMLWriterC object.");
      return nullptr;
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_Delete(vtkXMLWriterC* self)
  {
    if (self)
    {
      // Finalize the object.
      self->Writer = nullptr;
      self->DataObject = nullptr;
      delete self;
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_SetDataObjectType(vtkXMLWriterC* self, int objType)
  {
    if (!self)
    {
      return;
    }
    if (!self->DataObject)
    {
      // Create the writer and data object.
      switch (objType)
      {
        case VTK_POLY_DATA:
        {
          self->DataObject = vtkSmartPointer<vtkPolyData>::New();
          self->Writer = vtkSmartPointer<vtkXMLPolyDataWriter>::New();
        }
        break;
        case VTK_UNSTRUCTURED_GRID:
        {
          self->DataObject = vtkSmartPointer<vtkUnstructuredGrid>::New();
          self->Writer = vtkSmartPointer<vtkXMLUnstructuredGridWriter>::New();
        }
        break;
        case VTK_STRUCTURED_GRID:
        {
          self->DataObject = vtkSmartPointer<vtkStructuredGrid>::New();
          self->Writer = vtkSmartPointer<vtkXMLStructuredGridWriter>::New();
        }
        break;
        case VTK_RECTILINEAR_GRID:
        {
          self->DataObject = vtkSmartPointer<vtkRectilinearGrid>::New();
          self->Writer = vtkSmartPointer<vtkXMLRectilinearGridWriter>::New();
        }
        break;
        case VTK_IMAGE_DATA:
        {
          self->DataObject = vtkSmartPointer<vtkImageData>::New();
          self->Writer = vtkSmartPointer<vtkXMLImageDataWriter>::New();
        }
        break;
      }

      // Set the data object as input to the writer.
      if (self->Writer && self->DataObject)
      {
        self->Writer->SetInputData(self->DataObject);
      }
      else
      {
        vtkGenericWarningMacro(
          "Failed to allocate data object and writer for type " << objType << ".");
      }
    }
    else
    {
      vtkGenericWarningMacro("vtkXMLWriterC_SetDataObjectType called twice.");
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_SetDataModeType(vtkXMLWriterC* self, int datamodetype)
  {
    if (!self)
    {
      return;
    }
    if (self->Writer)
    {
      // Invoke the writer.
      switch (datamodetype)
      {
        case vtkXMLWriter::Ascii:
        case vtkXMLWriter::Binary:
        case vtkXMLWriter::Appended:
          self->Writer->SetDataMode(datamodetype);
          break;
        default:
          vtkGenericWarningMacro(
            "vtkXMLWriterC_SetDataModeType : unknown DataMode: " << datamodetype);
      }
    }
    else
    {
      vtkGenericWarningMacro(
        "vtkXMLWriterC_SetDataModeType called before vtkXMLWriterC_SetDataObjectType.");
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_SetExtent(vtkXMLWriterC* self, int extent[6])
  {
    if (!self)
    {
      return;
    }
    if (vtkImageData* imData = vtkImageData::SafeDownCast(self->DataObject))
    {
      imData->SetExtent(extent);
    }
    else if (vtkStructuredGrid* sGrid = vtkStructuredGrid::SafeDownCast(self->DataObject))
    {
      sGrid->SetExtent(extent);
    }
    else if (vtkRectilinearGrid* rGrid = vtkRectilinearGrid::SafeDownCast(self->DataObject))
    {
      rGrid->SetExtent(extent);
    }
    else if (self->DataObject)
    {
      vtkGenericWarningMacro("vtkXMLWriterC_SetExtent called for "
        << self->DataObject->GetClassName() << " data object.");
    }
    else
    {
      vtkGenericWarningMacro(
        "vtkXMLWriterC_SetExtent called before vtkXMLWriterC_SetDataObjectType.");
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_SetPoints(vtkXMLWriterC* self, int dataType, void* data, vtkIdType numPoints)
  {
    if (!self)
    {
      return;
    }
    if (vtkPointSet* dataObject = vtkPointSet::SafeDownCast(self->DataObject))
    {
      // Create the vtkDataArray that will reference the points.
      if (vtkSmartPointer<vtkDataArray> array =
            vtkXMLWriterC_NewDataArray("SetPoints", nullptr, dataType, data, numPoints, 3))
      {
        // Store the point array in the data object's points.
        if (vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New())
        {
          points->SetNumberOfPoints(numPoints);
          points->SetData(array);
          dataObject->SetPoints(points);
        }
        else
        {
          vtkGenericWarningMacro("vtkXMLWriterC_SetPoints failed to create a vtkPoints object.");
        }
      }
    }
    else if (self->DataObject)
    {
      vtkGenericWarningMacro("vtkXMLWriterC_SetPoints called for "
        << self->DataObject->GetClassName() << " data object.");
    }
    else
    {
      vtkGenericWarningMacro(
        "vtkXMLWriterC_SetPoints called before vtkXMLWriterC_SetDataObjectType.");
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_SetOrigin(vtkXMLWriterC* self, double origin[3])
  {
    if (!self)
    {
      return;
    }
    if (vtkImageData* dataObject = vtkImageData::SafeDownCast(self->DataObject))
    {
      dataObject->SetOrigin(origin);
    }
    else if (self->DataObject)
    {
      vtkGenericWarningMacro("vtkXMLWriterC_SetOrigin called for "
        << self->DataObject->GetClassName() << " data object.");
    }
    else
    {
      vtkGenericWarningMacro(
        "vtkXMLWriterC_SetOrigin called before vtkXMLWriterC_SetDataObjectType.");
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_SetSpacing(vtkXMLWriterC* self, double spacing[3])
  {
    if (!self)
    {
      return;
    }
    if (vtkImageData* dataObject = vtkImageData::SafeDownCast(self->DataObject))
    {
      dataObject->SetSpacing(spacing);
    }
    else if (self->DataObject)
    {
      vtkGenericWarningMacro("vtkXMLWriterC_SetSpacing called for "
        << self->DataObject->GetClassName() << " data object.");
    }
    else
    {
      vtkGenericWarningMacro(
        "vtkXMLWriterC_SetSpacing called before vtkXMLWriterC_SetDataObjectType.");
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_SetCoordinates(
    vtkXMLWriterC* self, int axis, int dataType, void* data, vtkIdType numCoordinates)
  {
    if (!self)
    {
      return;
    }
    if (vtkRectilinearGrid* dataObject = vtkRectilinearGrid::SafeDownCast(self->DataObject))
    {
      // Check the axis number.
      if (axis < 0 || axis > 2)
      {
        vtkGenericWarningMacro("vtkXMLWriterC_SetCoordinates called with invalid axis "
          << axis << ".  Use 0 for X, 1 for Y, and 2 for Z.");
      }

      // Create the vtkDataArray that will reference the coordinates.
      if (vtkSmartPointer<vtkDataArray> array = vtkXMLWriterC_NewDataArray(
            "SetCoordinates", nullptr, dataType, data, numCoordinates, 1))
      {
        switch (axis)
        {
          case 0:
            dataObject->SetXCoordinates(array);
            break;
          case 1:
            dataObject->SetYCoordinates(array);
            break;
          case 2:
            dataObject->SetZCoordinates(array);
            break;
        }
      }
    }
    else if (self->DataObject)
    {
      vtkGenericWarningMacro("vtkXMLWriterC_SetCoordinates called for "
        << self->DataObject->GetClassName() << " data object.");
    }
    else
    {
      vtkGenericWarningMacro(
        "vtkXMLWriterC_SetCoordinates called before vtkXMLWriterC_SetDataObjectType.");
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_SetCellsWithType(
    vtkXMLWriterC* self, int cellType, vtkIdType ncells, vtkIdType* cells, vtkIdType cellsSize)
  {
    if (!self)
    {
      return;
    }
    if (vtkPolyData* dataObject = vtkPolyData::SafeDownCast(self->DataObject))
    {
      // Create a cell array to reference the cells.
      if (vtkSmartPointer<vtkCellArray> cellArray =
            vtkXMLWriterC_NewCellArray("SetCellsWithType", ncells, cells, cellsSize))
      {
        // Store the cell array in the data object.
        if (cellType == VTK_VERTEX || cellType == VTK_POLY_VERTEX)
        {
          dataObject->SetVerts(cellArray);
        }
        else if (cellType == VTK_LINE || cellType == VTK_POLY_LINE)
        {
          dataObject->SetLines(cellArray);
        }
        else if (cellType == VTK_TRIANGLE || cellType == VTK_TRIANGLE_STRIP)
        {
          dataObject->SetStrips(cellArray);
        }
        else // if(cellType == VTK_POLYGON || cellType == VTK_QUAD)
        {
          dataObject->SetPolys(cellArray);
        }
      }
    }
    else if (vtkUnstructuredGrid* uGrid = vtkUnstructuredGrid::SafeDownCast(self->DataObject))
    {
      // Create a cell array to reference the cells.
      if (vtkSmartPointer<vtkCellArray> cellArray =
            vtkXMLWriterC_NewCellArray("SetCellsWithType", ncells, cells, cellsSize))
      {
        // Store the cell array in the data object.
        uGrid->SetCells(cellType, cellArray);
      }
    }
    else if (self->DataObject)
    {
      vtkGenericWarningMacro("vtkXMLWriterC_SetCellsWithType called for "
        << self->DataObject->GetClassName() << " data object.");
    }
    else
    {
      vtkGenericWarningMacro(
        "vtkXMLWriterC_SetCellsWithType called before vtkXMLWriterC_SetDataObjectType.");
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_SetCellsWithTypes(
    vtkXMLWriterC* self, int* cellTypes, vtkIdType ncells, vtkIdType* cells, vtkIdType cellsSize)
  {
    if (!self)
    {
      return;
    }
    if (vtkUnstructuredGrid* dataObject = vtkUnstructuredGrid::SafeDownCast(self->DataObject))
    {
      // Create a cell array to reference the cells.
      if (vtkSmartPointer<vtkCellArray> cellArray =
            vtkXMLWriterC_NewCellArray("SetCellsWithType", ncells, cells, cellsSize))
      {
        // Store the cell array in the data object.
        dataObject->SetCells(cellTypes, cellArray);
      }
    }
    else if (self->DataObject)
    {
      vtkGenericWarningMacro("vtkXMLWriterC_SetCellsWithTypes called for "
        << self->DataObject->GetClassName() << " data object.");
    }
    else
    {
      vtkGenericWarningMacro(
        "vtkXMLWriterC_SetCellsWithTypes called before vtkXMLWriterC_SetDataObjectType.");
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_SetPointData(vtkXMLWriterC* self, const char* name, int dataType, void* data,
    vtkIdType numTuples, int numComponents, const char* role)
  {
    vtkXMLWriterC_SetDataInternal(
      self, name, dataType, data, numTuples, numComponents, role, "SetPointData", 1);
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_SetCellData(vtkXMLWriterC* self, const char* name, int dataType, void* data,
    vtkIdType numTuples, int numComponents, const char* role)
  {
    vtkXMLWriterC_SetDataInternal(
      self, name, dataType, data, numTuples, numComponents, role, "SetCellData", 0);
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_SetFileName(vtkXMLWriterC* self, const char* fileName)
  {
    if (!self)
    {
      return;
    }
    if (self->Writer)
    {
      // Store the file name.
      self->Writer->SetFileName(fileName);
    }
    else
    {
      vtkGenericWarningMacro(
        "vtkXMLWriterC_SetFileName called before vtkXMLWriterC_SetDataObjectType.");
    }
  }

  //----------------------------------------------------------------------------
  int vtkXMLWriterC_Write(vtkXMLWriterC* self)
  {
    if (!self)
    {
      return 0;
    }
    if (self->Writer)
    {
      // Invoke the writer.
      return self->Writer->Write();
    }
    else
    {
      vtkGenericWarningMacro("vtkXMLWriterC_Write called before vtkXMLWriterC_SetDataObjectType.");
      return 0;
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_SetNumberOfTimeSteps(vtkXMLWriterC* self, int numTimeSteps)
  {
    if (!self)
    {
      return;
    }
    if (self->Writer)
    {
      // Set the number of time steps on the writer.
      self->Writer->SetNumberOfTimeSteps(numTimeSteps);
    }
    else
    {
      vtkGenericWarningMacro(
        "vtkXMLWriterC_SetNumberOfTimeSteps called before vtkXMLWriterC_SetDataObjectType.");
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_Start(vtkXMLWriterC* self)
  {
    if (!self)
    {
      return;
    }
    if (self->Writing)
    {
      vtkGenericWarningMacro(
        "vtkXMLWriterC_Start called multiple times without vtkXMLWriterC_Stop.");
    }
    else if (self->Writer)
    {
      // Check the conditions.
      if (self->Writer->GetNumberOfTimeSteps() == 0)
      {
        vtkGenericWarningMacro("vtkXMLWriterC_Start called with no time steps.");
      }
      else if (self->Writer->GetFileName() == nullptr)
      {
        vtkGenericWarningMacro("vtkXMLWriterC_Start called before vtkXMLWriterC_SetFileName.");
      }
      else
      {
        // Tell the writer to start writing.
        self->Writer->Start();
        self->Writing = 1;
      }
    }
    else
    {
      vtkGenericWarningMacro("vtkXMLWriterC_Start called before vtkXMLWriterC_SetDataObjectType.");
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_WriteNextTimeStep(vtkXMLWriterC* self, double timeValue)
  {
    if (!self)
    {
      return;
    }
    if (!self->Writing)
    {
      vtkGenericWarningMacro("vtkXMLWriterC_WriteNextTimeStep called before vtkXMLWriterC_Start.");
    }
    else if (self->Writer)
    {
      // Tell the writer to write this time step.
      self->Writer->WriteNextTime(timeValue);
    }
    else
    {
      vtkGenericWarningMacro("vtkXMLWriterC_Stop called before vtkXMLWriterC_SetDataObjectType.");
    }
  }

  //----------------------------------------------------------------------------
  void vtkXMLWriterC_Stop(vtkXMLWriterC* self)
  {
    if (!self)
    {
      return;
    }
    if (!self->Writing)
    {
      vtkGenericWarningMacro("vtkXMLWriterC_Stop called before vtkXMLWriterC_Start.");
    }
    else if (self->Writer)
    {
      // Tell the writer to stop writing.
      self->Writer->Stop();
      self->Writing = 0;
    }
    else
    {
      vtkGenericWarningMacro("vtkXMLWriterC_Stop called before vtkXMLWriterC_SetDataObjectType.");
    }
  }

} /* extern "C" */

//------------------------------------------------------------------------------
static vtkSmartPointer<vtkDataArray> vtkXMLWriterC_NewDataArray(const char* method,
  const char* name, int dataType, void* data, vtkIdType numTuples, int numComponents)
{
  // Create the vtkDataArray that will reference the data.
  vtkSmartPointer<vtkDataArray> array = vtkDataArray::CreateDataArray(dataType);
  if (array)
  {
    array->Delete();
  }
  if (!array || array->GetDataType() != dataType)
  {
    vtkGenericWarningMacro(
      "vtkXMLWriterC_" << method << " could not allocate array of type " << dataType << ".");
    return nullptr;
  }

  // Set the number of components.
  array->SetNumberOfComponents(numComponents);

  // Set the name if one was given.
  array->SetName(name);

  // Point the array at the given data.  It is not copied.
  array->SetVoidArray(data, numTuples * numComponents, 1);

  // Return the array.
  return array;
}

//------------------------------------------------------------------------------
static vtkSmartPointer<vtkCellArray> vtkXMLWriterC_NewCellArray(
  const char* method, vtkIdType ncells, vtkIdType* cells, vtkIdType cellsSize)
{
  // Create a vtkIdTypeArray to reference the cells.
  vtkSmartPointer<vtkIdTypeArray> array = vtkSmartPointer<vtkIdTypeArray>::New();
  if (!array)
  {
    vtkGenericWarningMacro("vtkXMLWriterC_" << method << " failed to allocate a vtkIdTypeArray.");
    return nullptr;
  }
  array->SetArray(cells, ncells * cellsSize, 1);

  // Create the cell array.
  vtkSmartPointer<vtkCellArray> cellArray = vtkSmartPointer<vtkCellArray>::New();
  if (!cellArray)
  {
    vtkGenericWarningMacro("vtkXMLWriterC_" << method << " failed to allocate a vtkCellArray.");
    return nullptr;
  }
  cellArray->AllocateExact(ncells, array->GetNumberOfValues() - ncells);
  cellArray->ImportLegacyFormat(array);
  return cellArray;
}

//------------------------------------------------------------------------------
static void vtkXMLWriterC_SetDataInternal(vtkXMLWriterC* self, const char* name, int dataType,
  void* data, vtkIdType numTuples, int numComponents, const char* role, const char* method,
  int isPoints)
{
  if (!self)
  {
    return;
  }
  if (vtkDataSet* dataObject = vtkDataSet::SafeDownCast(self->DataObject))
  {
    if (vtkSmartPointer<vtkDataArray> array =
          vtkXMLWriterC_NewDataArray(method, name, dataType, data, numTuples, numComponents))
    {
      // Store either in point data or cell data.
      vtkDataSetAttributes* dsa;
      if (isPoints)
      {
        dsa = dataObject->GetPointData();
      }
      else
      {
        dsa = dataObject->GetCellData();
      }

      // Store the data array with the requested role.
      if (role && strcmp(role, "SCALARS") == 0)
      {
        dsa->SetScalars(array);
      }
      else if (role && strcmp(role, "VECTORS") == 0)
      {
        dsa->SetVectors(array);
      }
      else if (role && strcmp(role, "NORMALS") == 0)
      {
        dsa->SetNormals(array);
      }
      else if (role && strcmp(role, "TENSORS") == 0)
      {
        dsa->SetTensors(array);
      }
      else if (role && strcmp(role, "TCOORDS") == 0)
      {
        dsa->SetTCoords(array);
      }
      else
      {
        dsa->AddArray(array);
      }
    }
  }
  else if (self->DataObject)
  {
    vtkGenericWarningMacro("vtkXMLWriterC_" << method << " called for "
                                            << self->DataObject->GetClassName() << " data object.");
  }
  else
  {
    vtkGenericWarningMacro(
      "vtkXMLWriterC_" << method << " called before vtkXMLWriterC_SetDataObjectType.");
  }
}
