/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
#ifndef itkVTKPolyDataReader_hxx
#define itkVTKPolyDataReader_hxx

#include "itkMath.h"
#include <fstream>
#include <cstdio>
#include <cstring>

namespace itk
{
//
// Constructor
//
template <typename TOutputMesh>
VTKPolyDataReader<TOutputMesh>::VTKPolyDataReader()
{
  //
  // Create the output
  //
  auto output = TOutputMesh::New();
  this->ProcessObject::SetNumberOfRequiredOutputs(1);
  this->ProcessObject::SetNthOutput(0, output.GetPointer());
}

template <typename TOutputMesh>
void
VTKPolyDataReader<TOutputMesh>::GenerateData()
{
  const typename OutputMeshType::Pointer outputMesh = this->GetOutput();

  outputMesh->SetCellsAllocationMethod(MeshEnums::MeshClassCellsAllocationMethod::CellsAllocatedDynamicallyCellByCell);

  if (m_FileName.empty())
  {
    itkExceptionStringMacro("No input FileName");
  }

  //
  // Read input file
  //
  std::ifstream inputFile(m_FileName.c_str());

  if (!inputFile.is_open())
  {
    itkExceptionMacro("Unable to open file\ninputFilename= " << m_FileName);
  }

  inputFile.imbue(std::locale::classic());
  std::string line;

  // The first line must be "# vtk DataFile Version x.x" where x.x can
  // vary
  std::getline(inputFile, m_Version, '\n');
  if (inputFile.fail())
  {
    itkExceptionMacro("Error reading file: " << m_FileName << "\nUnexpected end-of-file trying to read first line.");
  }
  if (m_Version.find("# vtk DataFile Version ") == std::string::npos)
  {
    itkExceptionMacro(
      << "Error reading file: " << m_FileName << "\nOnly vtk legacy format files can be read."
      << "\nThis file does not start with the line: # vtk DataFile Version x.x where x.x is the version.");
  }

  // Next is a one line description
  std::getline(inputFile, m_Header, '\n');
  if (inputFile.eof())
  {
    itkExceptionMacro("Error reading file: " << m_FileName << "\nUnexpected end-of-file trying to read header.");
  }

  // Next is the file format
  std::getline(inputFile, line, '\n');
  if (inputFile.eof())
  {
    itkExceptionMacro("Error reading file: " << m_FileName << "\nUnexpected end-of-file trying to file format.");
  }
  if (line.find("ASCII") == std::string::npos)
  {
    itkExceptionMacro("Error reading file: " << m_FileName << "\nFile format is " << line
                                             << " but only ASCII files can be read.");
  }

  bool foundPoints = false;
  while (!inputFile.eof())
  {
    std::getline(inputFile, line, '\n');

    if (line.find("POINTS") != std::string::npos)
    {
      foundPoints = true;
      break;
    }
  }

  if (!foundPoints)
  {
    itkExceptionMacro("Error reading file: " << m_FileName << "\nUnexpected end-of-file before finding POINTS.");
  }
  itkDebugMacro("POINTS line" << line);

  const std::string pointLine(line, strlen("POINTS "), line.length());
  itkDebugMacro("pointLine " << pointLine);

  // we must use long here because this is the exact type specified by scanf
  long numberOfPoints = PointIdentifier{};

  if (sscanf(pointLine.c_str(), "%ld", &numberOfPoints) != 1)
  {
    itkExceptionMacro("Error reading file: " << m_FileName << "\nFailed to read numberOfPoints.\n"
                                             << "       pointLine= " << pointLine);
  }

  itkDebugMacro("numberOfPoints= " << numberOfPoints);

  if (numberOfPoints < 1)
  {
    itkExceptionMacro("Error reading file: " << m_FileName << "numberOfPoints < 1"
                                             << "       numberOfPoints line= " << numberOfPoints);
  }

  outputMesh->GetPoints()->Reserve(itk::Math::CastWithRangeCheck<PointIdentifier>(numberOfPoints));

  //
  // Load the point coordinates into the itk::Mesh
  //

  PointType point;

  for (PointIdentifier i = 0; i < itk::Math::CastWithRangeCheck<PointIdentifier>(numberOfPoints); ++i)
  {
    inputFile >> point;
    if (inputFile.eof())
    {
      itkExceptionMacro("Error while reading file: " << m_FileName
                                                     << "\nUnexpected end-of-file trying to read points.");
    }
    if (inputFile.fail())
    {
      itkExceptionMacro("Error reading file: " << m_FileName << "\nInput could not be interpreted as a point.");
    }
    outputMesh->SetPoint(i, point);
  }

  // Continue searching for the POLYGONS line
  bool foundPolygons = false;
  while (!inputFile.eof())
  {
    std::getline(inputFile, line, '\n');
    if (line.find("POLYGONS") != std::string::npos)
    {
      foundPolygons = true;
      break;
    }
  }

  if (!foundPolygons)
  {
    itkExceptionMacro("Error reading file: " << m_FileName << "\nUnexpected end-of-file before finding POLYGONS.");
  }

  itkDebugMacro("POLYGONS line" << line);

  const std::string polygonLine(line, strlen("POLYGONS "), line.length());
  itkDebugMacro("polygonLine " << polygonLine);

  //
  // Read the number of polygons
  //

  // we must use long here because this is the exact type specified by scanf
  long numberOfPolygons = CellIdentifier{};
  long numberOfIndices = CellIdentifier{};

  if (sscanf(polygonLine.c_str(), "%ld %ld", &numberOfPolygons, &numberOfIndices) != 2)
  {
    itkExceptionMacro("Error reading file: " << m_FileName << "\nFailed to read numberOfPolygons from subline2"
                                             << "\npolygonLine = " << polygonLine);
  }

  itkDebugMacro("numberOfPolygons " << numberOfPolygons);
  itkDebugMacro("numberOfIndices " << numberOfIndices);

  if (numberOfPolygons < 1)
  {
    itkExceptionMacro("Error reading file: " << m_FileName
                                             << "\nnumberOfPolygons < 1\nnumberOfPolygons= " << numberOfPolygons);
  }

  if (numberOfIndices < numberOfPolygons)
  {
    itkExceptionMacro("Error reading file: " << m_FileName << "\nnumberOfIndices < numberOfPolygons\n"
                                             << "numberOfIndices= " << numberOfIndices << '\n'
                                             << "numberOfPolygons= " << numberOfPolygons);
  }

  //
  // Load the polygons into the itk::Mesh
  //

  long numberOfCellPoints = 0;
  long ids[3]; // need a signed type on input.

  for (CellIdentifier i = 0; i < itk::Math::CastWithRangeCheck<CellIdentifier>(numberOfPolygons); ++i)
  {
    std::getline(inputFile, line, '\n');
    if (inputFile.eof())
    {
      itkExceptionMacro("Error reading file: " << m_FileName << "\nFailed to read " << numberOfPolygons
                                               << " polygons before the end of file."
                                               << " Only read " << i + 1);
    }

    if (line.find("DATA") != std::string::npos)
    {
      itkExceptionMacro("Error reading file: " << m_FileName << "\nRead keyword DATA");
    }

    int got = 0;
    if ((got = sscanf(line.c_str(), "%ld %ld %ld %ld", &numberOfCellPoints, &ids[0], &ids[1], &ids[2])) != 4)
    {
      itkExceptionMacro("Error reading file: "
                        << m_FileName << "\nError parsing POLYGON cell. Expected 4 items but got " << got << std::endl
                        << "Line is: " << line);
    }

    if (numberOfCellPoints != 3)
    {
      itkExceptionMacro("Error reading file: " << m_FileName << "\nnumberOfCellPoints != 3\n"
                                               << "numberOfCellPoints= " << numberOfCellPoints
                                               << ". VTKPolyDataReader can only read triangles");
    }

    if ((ids[0] < 0) || (ids[1] < 0) || (ids[2] < 0))
    {
      itkExceptionMacro("Error reading file: " << m_FileName
                                               << "point ids must be >= 0.\n"
                                                  "ids="
                                               << ids[0] << ' ' << ids[1] << ' ' << ids[2]);
    }

    const auto signedNumberOfPoints = itk::Math::CastWithRangeCheck<OffsetValueType>(numberOfPoints);
    if (ids[0] >= signedNumberOfPoints || ids[1] >= signedNumberOfPoints || ids[2] >= signedNumberOfPoints)
    {
      itkExceptionMacro("Error reading file: " << m_FileName
                                               << "Point ids must be < number of points: " << numberOfPoints
                                               << "\nids= " << ids[0] << ' ' << ids[1] << ' ' << ids[2]);
    }

    CellAutoPointer cell;
    auto *          triangleCell = new TriangleCellType;
    for (PointIdentifier k = 0; k < 3; ++k)
    {
      triangleCell->SetPointId(k, ids[k]);
    }

    cell.TakeOwnership(triangleCell);
    outputMesh->SetCell(i, cell);
  }

  bool foundPointData = false;

  while (!inputFile.eof())
  {
    std::getline(inputFile, line, '\n');

    if (line.find("POINT_DATA") != std::string::npos)
    {
      foundPointData = true;
      break;
    }
  }

  if (foundPointData)
  {
    using PointDataContainer = typename OutputMeshType::PointDataContainer;

    outputMesh->SetPointData(PointDataContainer::New());
    outputMesh->GetPointData()->Reserve(itk::Math::CastWithRangeCheck<PointIdentifier>(numberOfPoints));

    itkDebugMacro("POINT_DATA line" << line);

    // Skip two lines
    if (!inputFile.eof())
    {
      std::getline(inputFile, line, '\n');
    }
    else
    {
      itkExceptionMacro("Error reading file: " << m_FileName
                                               << "\nUnexpected end-of-file while trying to read POINT_DATA.");
    }
    if (!inputFile.eof())
    {
      std::getline(inputFile, line, '\n');
    }
    else
    {
      itkExceptionMacro("Error reading file: " << m_FileName
                                               << "\nUnexpected end-of-file while trying to read POINT_DATA.");
    }

    double pointData = NAN;

    for (PointIdentifier pid = 0; pid < itk::Math::CastWithRangeCheck<PointIdentifier>(numberOfPoints); ++pid)
    {
      if (inputFile.eof())
      {
        itkExceptionMacro("Error reading file: " << m_FileName
                                                 << "\nUnexpected end-of-file while trying to read POINT_DATA."
                                                 << "Failed while trying to reading point data for id: " << pid);
      }
      inputFile >> pointData;
      outputMesh->SetPointData(pid, pointData);
    }
  }
  inputFile.close();
}

template <typename TOutputMesh>
void
VTKPolyDataReader<TOutputMesh>::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);

  os << indent << "FileName: " << m_FileName << std::endl;
  os << indent << "Version: " << m_Version << std::endl;
  os << indent << "Header: " << m_Header << std::endl;
}
} // end of namespace itk

#endif
