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

#include "vtkAppendPolyData.h"
#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkDoubleArray.h"
#include "vtkErrorCode.h"
#include "vtkGarbageCollector.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkSmartPointer.h"
#include "vtkStringScanner.h"
#include "vtkUnsignedIntArray.h"

#include <vtksys/FStream.hxx>
#include <vtksys/SystemTools.hxx>

#include <sstream>
#include <string>
#include <vector>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkFacetReader);

//------------------------------------------------------------------------------
// Due to a buggy stream library on the HP and another on Mac OS X, we
// need this very carefully written version of getline.  Returns true
// if any data were read before the end-of-file was reached.
//
static bool GetLineFromStream(istream& is, std::string& line, bool* has_newline = nullptr)
{
  constexpr int bufferSize = 1024;
  char buffer[bufferSize];
  line = "";
  bool haveData = false;
  if (has_newline)
  {
    *has_newline = false;
  }

  // If no characters are read from the stream, the end of file has
  // been reached.
  while (((void)(is.getline(buffer, bufferSize)), (is.gcount() > 0)))
  {
    haveData = true;
    line.append(buffer);

    // If newline character was read, the gcount includes the
    // character, but the buffer does not.  The end of line has been
    // reached.
    if (strlen(buffer) < static_cast<size_t>(is.gcount()))
    {
      if (has_newline)
      {
        *has_newline = true;
      }
      break;
    }

    // The fail bit may be set.  Clear it.
    is.clear(is.rdstate() & ~ios::failbit);
  }
  return haveData;
}

//------------------------------------------------------------------------------
vtkFacetReader::vtkFacetReader()
{
  this->FileName = nullptr;
  this->SetNumberOfInputPorts(0);
}

//------------------------------------------------------------------------------
vtkFacetReader::~vtkFacetReader()
{
  delete[] this->FileName;
}

//------------------------------------------------------------------------------
int vtkFacetReader::CanReadFile(const char* filename)
{
  vtksys::SystemTools::Stat_t fs;
  if (vtksys::SystemTools::Stat(filename, &fs))
  {
    // Specified filename not found
    return 0;
  }

  vtksys::ifstream ifs(filename, ios::in);
  if (!ifs)
  {
    // Specified filename not found
    return 0;
  }

  std::string line;
  // Read first row
  if (!GetLineFromStream(ifs, line))
  {
    // Cannot read file comment
    return 0;
  }

  // File starts with FACET FILE
  return (line.find("FACET FILE") == 0);
}

//------------------------------------------------------------------------------
int vtkFacetReader::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
  // get the info object
  vtkInformation* outInfo = outputVector->GetInformationObject(0);

  // get the output
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));

  if (!this->FileName)
  {
    vtkErrorMacro("No filename specified");
    return 1;
  }

  vtksys::SystemTools::Stat_t fs;
  if (vtksys::SystemTools::Stat(this->FileName, &fs))
  {
    this->SetErrorCode(vtkErrorCode::FileNotFoundError);
    vtkErrorMacro("Specified filename not found");
    return 1;
  }

  vtksys::ifstream ifs(this->FileName, ios::in);
  if (!ifs)
  {
    this->SetErrorCode(vtkErrorCode::FileNotFoundError);
    vtkErrorMacro("Specified filename not found");
    return 1;
  }

  vtkDebugMacro(<< "Reading Facet file");
  std::string line;

  // Read first row
  if (!GetLineFromStream(ifs, line))
  {
    vtkErrorMacro("Cannot read file comment");
    return 1;
  }

  // Read number of parts
  int num_parts = 0;
  if (!GetLineFromStream(ifs, line) || vtk::from_chars(line, num_parts).ec != std::errc{} ||
    num_parts < 0)
  {
    vtkErrorMacro("Bad number of parts line");
    return 1;
  }

  vtkDebugMacro("Number of parts is: " << num_parts);

  // Buffers for various information from file
  std::vector<vtkIdType> pointList;
  std::vector<char> stringBuffer;

  // We will need append individual parts together. Once multiblock is
  // supported, this should go out.
  vtkSmartPointer<vtkAppendPolyData> appendPtr = vtkSmartPointer<vtkAppendPolyData>::New();
  appendPtr->SetContainerAlgorithm(this);

  // Block garbage collection so that appends will not take too long.
  vtkGarbageCollector::DeferredCollectionPush();

  int part;
  int error = 0;
  bool abort = false;

  // Loop through individual parts
  for (part = 0; part < num_parts || error; part++)
  {
    if (this->CheckAbort())
    {
      abort = true;
      break;
    }
    std::string partName;
    vtkDebugMacro("Reading part: " << part);

    // Read part name
    if (!GetLineFromStream(ifs, partName))
    {
      vtkErrorMacro("Cannot read part name");
      error = 1;
      break;
    }
    vtkDebugMacro("Part name: " << partName);

    // Read cell/point index and geometry information including the number of
    // points. cell/point index for points is always 0
    int cell_point_index = -1;
    vtk::scan_result_type<std::string&, int, int, int> resultNumPoints;
    if (!GetLineFromStream(ifs, line) ||
      vtk::from_chars(line, cell_point_index).ec != std::errc{} || cell_point_index != 0 ||
      !GetLineFromStream(ifs, line) ||
      !((resultNumPoints = vtk::scan<int, int, int>(line, "{:d} {:d} {:d}"))))
    {
      vtkErrorMacro("Problem reading number of points");
      error = 1;
      break;
    }
    auto& [numpts, _1, _2] = resultNumPoints->values();
    if (numpts < 0)
    {
      vtkErrorMacro("Problem reading number of points");
      error = 1;
      break;
    }

    vtkIdType num_points = numpts;
    vtkIdType point;
    vtkSmartPointer<vtkPoints> myPointsPtr = vtkSmartPointer<vtkPoints>::New();

    // Read individual points
    for (point = 0; point < num_points; point++)
    {
      if (this->CheckAbort())
      {
        abort = true;
        break;
      }
      // Read point
      vtk::scan_result_type<std::string&, double, double, double> pointResult;
      if (!GetLineFromStream(ifs, line) ||
        !((pointResult = vtk::scan<double, double, double>(line, "{} {} {}"))))
      {
        vtkErrorMacro("Problem reading point: " << point << " " << line);
        error = 1;
        break;
      }
      auto& [x, y, z] = pointResult->values();
      myPointsPtr->InsertNextPoint(x, y, z);
    }
    if (error)
    {
      break;
    }

    // Read cell point index
    if (!GetLineFromStream(ifs, line) ||
      vtk::from_chars(line, cell_point_index).ec != std::errc{} || cell_point_index != 1)
    {
      vtkErrorMacro("Cannot read cell/point index or it is not 1");
      error = 1;
      break;
    }

    // Read part name
    if (!GetLineFromStream(ifs, line) || partName != line)
    {
      vtkErrorMacro("Cannot read part name or the part name does not match");
      error = 1;
      break;
    }

    // Read topology information
    vtk::scan_result_type<std::string&, int, int> resultNumCells;
    if (!GetLineFromStream(ifs, line) ||
      !((resultNumCells = vtk::scan<int, int>(line, "{:d} {:d}"))))
    {
      vtkErrorMacro("Problem reading number of cells and points per cell");
      error = 1;
      break;
    }
    auto& [numcells, numpointpercell] = resultNumCells->values();
    if (numcells < 0 || numpointpercell < 0)
    {
      vtkErrorMacro("Problem reading number of cells and points per cell");
      error = 1;
      break;
    }

    vtkIdType num_cells = numcells;
    vtkIdType num_points_per_cell = numpointpercell;
    if (pointList.size() < static_cast<size_t>(num_points_per_cell))
    {
      pointList.resize(num_points_per_cell);
    }

    // We need arrays to store material and part number
    vtkSmartPointer<vtkUnsignedIntArray> materialArrayPtr =
      vtkSmartPointer<vtkUnsignedIntArray>::New();
    materialArrayPtr->SetName("Material");
    materialArrayPtr->SetNumberOfComponents(1);
    materialArrayPtr->SetNumberOfTuples(num_cells);

    vtkSmartPointer<vtkUnsignedIntArray> relativePartArrayPtr =
      vtkSmartPointer<vtkUnsignedIntArray>::New();
    relativePartArrayPtr->SetName("RelativePartNumber");
    relativePartArrayPtr->SetNumberOfComponents(1);
    relativePartArrayPtr->SetNumberOfTuples(num_cells);

    vtkSmartPointer<vtkCellArray> myCellsPtr = vtkSmartPointer<vtkCellArray>::New();

    // Read cells
    vtkIdType cell;
    for (cell = 0; cell < num_cells; cell++)
    {
      if (this->CheckAbort())
      {
        abort = true;
        break;
      }
      // Read cell
      if (!GetLineFromStream(ifs, line))
      {
        vtkErrorMacro("Cannot read cell: " << cell);
        error = 1;
        break;
      }

      // Read specified number of points from cell information
      if (stringBuffer.size() < line.size() + 1)
      {
        stringBuffer.resize(line.size() + 1);
      }
      char* strPtr = &(*stringBuffer.begin());
      strcpy(strPtr, line.c_str());
      std::string str(strPtr, stringBuffer.size());
      std::istringstream lineStream(str);
      vtkIdType kk;
      int material = -1, relativePartNumber = -1;
      for (kk = 0; kk < num_points_per_cell; kk++)
      {
        int val;
        if (!(lineStream >> val))
        {
          vtkErrorMacro("Cannot extract cell points for cell: " << cell);
          error = 1;
          break;
        }
        // point indices start with 0, while cell descriptions have point
        // indices starting with 1
        pointList[kk] = val - 1;
      }

      // Extract material and part number
      if (!(lineStream >> material >> relativePartNumber))
      {
        vtkErrorMacro("Cannot extract cell material and part for cell: " << cell);
        error = 1;
        break;
      }
      materialArrayPtr->SetTuple1(cell, material);
      relativePartArrayPtr->SetTuple1(cell, relativePartNumber);

      myCellsPtr->InsertNextCell(num_points_per_cell, &(*pointList.begin()));
    }
    if (error)
    {
      break;
    }

    vtkIdType cc;

    // Create another array with absolute part number
    vtkUnsignedIntArray* partNumberArray = vtkUnsignedIntArray::New();
    partNumberArray->SetName("PartNumber");
    partNumberArray->SetNumberOfComponents(1);
    partNumberArray->SetNumberOfTuples(num_cells);
    for (cc = 0; cc < partNumberArray->GetNumberOfTuples(); cc++)
    {
      partNumberArray->SetTuple1(cc, part);
    }

    // Create part and store it
    vtkPolyData* partGrid = vtkPolyData::New();
    switch (num_points_per_cell)
    {
      case 1:
        partGrid->SetVerts(myCellsPtr);
        break;
      case 2:
        partGrid->SetLines(myCellsPtr);
        break;
      case 3:
        partGrid->SetPolys(myCellsPtr);
        break;
      default:
        partGrid->SetPolys(myCellsPtr);
        break;
    }
    partGrid->SetPoints(myPointsPtr);
    partGrid->GetCellData()->AddArray(partNumberArray);
    partGrid->GetCellData()->AddArray(materialArrayPtr);
    partGrid->GetCellData()->AddArray(relativePartArrayPtr);
    partGrid->GetCellData()->SetScalars(materialArrayPtr);
    appendPtr->AddInputData(partGrid);

    partNumberArray->Delete();
    partGrid->Delete();
  }

  if (!abort && !error)
  {
    // If everything ok, use append.
    appendPtr->Update();
    output->ShallowCopy(appendPtr->GetOutput());
  }

  // Release garbage collection
  vtkGarbageCollector::DeferredCollectionPop();
  vtkDebugMacro("Done reading file: " << this->FileName);

  return 1;
}

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

  os << indent << "File Name: " << (this->FileName ? this->FileName : "(none)") << "\n";
}
VTK_ABI_NAMESPACE_END
