/*=========================================================================

  Program:   Insight Segmentation & Registration Toolkit
  Module:    $RCSfile: itkVTKTetrahedralMeshReader.hxx,v $
  Language:  C++
  Date:      $Date: 2011-10-05 18:01:00 $
  Version:   $Revision: 1.19 $

  Copyright (c) Insight Software Consortium. All rights reserved.
  See ITKCopyright.txt or https://www.itk.org/HTML/Copyright.htm for details.

     This software is distributed WITHOUT ANY WARRANTY; without even
     the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
     PURPOSE.  See the above copyright notices for more information.

=========================================================================*/
#ifndef itkVTKTetrahedralMeshReader_hxx
#define itkVTKTetrahedralMeshReader_hxx

#include <fstream>
#include <cstdio>
#include <cstring>

namespace itk
{

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

//
// Destructor
//
template <typename TOutputMesh>
VTKTetrahedralMeshReader<TOutputMesh>::~VTKTetrahedralMeshReader() = default;

template <typename TOutputMesh>
void
VTKTetrahedralMeshReader<TOutputMesh>::GenerateData()
{
  OutputMeshType * 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);

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

  unsigned long numberOfPoints{};

  if (sscanf(pointLine.c_str(), "%lu", &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);
  }

  PointsContainer * points = outputMesh->GetPoints();
  points->Reserve(numberOfPoints);

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

  PointType point;

  for (PointIdentifier pointId = 0; pointId < numberOfPoints; ++pointId)
  {
    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(pointId, point);
  }

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

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

  itkDebugMacro("CELLS line" << line);

  std::string cellsLine(line, strlen("CELLS "), line.length());
  itkDebugMacro("cellsLine " << cellsLine);

  //
  // Read the number of cells
  //

  unsigned long numberOfCells{};
  unsigned long numberOfIndices{};

  if (sscanf(cellsLine.c_str(), "%lu %lu", &numberOfCells, &numberOfIndices) != 2)
  {
    itkExceptionMacro("Error reading file: " << m_FileName << "\nFailed to read numberOfCells from subline2"
                                             << "\ncellsLine = " << cellsLine);
  }

  itkDebugMacro("numberOfCells " << numberOfCells);
  itkDebugMacro("numberOfIndices " << numberOfIndices);

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

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

  //
  // Load the cells into the itk::Mesh
  //

  unsigned long numberOfCellPoints;
  long          ids[4];

  for (CellIdentifier cellId = 0; cellId < numberOfCells; ++cellId)
  {
    std::getline(inputFile, line, '\n');
    if (inputFile.eof())
    {
      itkExceptionMacro("Error reading file: " << m_FileName << "\nFailed to read " << numberOfCells
                                               << " cells before the end of file."
                                               << " Only read " << cellId + 1);
    }

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

    unsigned long numberOfPointsFound;
    if ((numberOfPointsFound =
           sscanf(line.c_str(), "%lu %ld %ld %ld %ld", &numberOfCellPoints, &ids[0], &ids[1], &ids[2], &ids[3])) != 5)
    {
      itkExceptionMacro("Error reading file: " << m_FileName
                                               << "\nError parsing Tetrahedron CELLS . Expected 5 items but got "
                                               << numberOfPointsFound << std::endl
                                               << "Line is: " << line);
    }

    if (numberOfCellPoints != 4)
    {
      itkExceptionMacro("Error reading file: " << m_FileName << "\nnumberOfCellPoints != 4\n"
                                               << "numberOfCellPoints= " << numberOfCellPoints << "\ncellId = "
                                               << cellId << ". VTKTetrahedralMeshReader can only read tetrahedra");
    }

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

    if (static_cast<PointIdentifier>(ids[0]) >= numberOfPoints ||
        static_cast<PointIdentifier>(ids[1]) >= numberOfPoints ||
        static_cast<PointIdentifier>(ids[2]) >= numberOfPoints ||
        static_cast<PointIdentifier>(ids[3]) >= numberOfPoints)
    {
      itkExceptionMacro("Error reading file: " << m_FileName << "Point ids must be < number of points: "
                                               << numberOfPoints << "\nids= " << ids[0] << ' ' << ids[1] << ' '
                                               << ids[2] << ' ' << ids[3]);
    }

    CellAutoPointer cell;

    auto * tetrahedronCell = new TetrahedronCellType;
    for (PointIdentifier pointId = 0; pointId < 4; ++pointId)
    {
      tetrahedronCell->SetPointId(pointId, ids[pointId]);
    }

    cell.TakeOwnership(tetrahedronCell);
    outputMesh->SetCell(cellId, cell);
  }


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

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

  itkDebugMacro("CELL_TYPES line" << line);

  std::string cellsTypesLine(line, strlen("CELL_TYPES "), line.length());

  itkDebugMacro("cellsTypesLine " << cellsTypesLine);


  unsigned int numberOfCellTypes = 0;
  if (sscanf(cellsTypesLine.c_str(), "%u", &numberOfCellTypes) != 1)
  {
    itkExceptionMacro("Error reading file: " << m_FileName << "\nFailed to read numberOfCellTypes from subline2"
                                             << "\ncellsTypesLine = " << cellsTypesLine);
  }


  if (static_cast<CellIdentifier>(numberOfCellTypes) != numberOfCells)
  {
    itkExceptionMacro("Error reading file: " << m_FileName << "\nnumberOfCellTypes < numberOfCells ");
  }


  for (CellIdentifier cellId = 0; cellId < numberOfCellTypes; ++cellId)
  {
    std::getline(inputFile, line, '\n');
    if (inputFile.eof())
    {
      itkExceptionMacro("Error reading file: " << m_FileName << "\nFailed to read " << numberOfCellTypes
                                               << " cells before the end of file."
                                               << " Only read " << cellId + 1);
    }


    int cellTypeID = 0;
    int cellTypeFound;
    if ((cellTypeFound = sscanf(line.c_str(), "%d ", &cellTypeID)) != 1)
    {
      itkExceptionMacro("Error reading file: " << m_FileName << "\nError parsing Cells types. Expected 1 item but got "
                                               << cellTypeFound << std::endl
                                               << "Line is: " << line);
    }


    constexpr int tetrahedralCellTypeID{ 10 };

    if (cellTypeID != tetrahedralCellTypeID)
    {
      itkExceptionMacro("Error reading file: " << m_FileName << "\nCell type != 10\n"
                                               << "Cell type = " << cellTypeID
                                               << ". VTKTetrahedralMeshReader can only read tetrahedra");
    }
  }

  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;
    using PointDataContainerPointer = typename PointDataContainer::Pointer;

    PointDataContainerPointer pointDataContainer = PointDataContainer::New();
    pointDataContainer->Reserve(numberOfPoints);

    outputMesh->SetPointData(pointDataContainer);


    itkDebugMacro("POINT_DATA line" << line);

    // Skip two lines
    for (int j = 0; j < 2; ++j)
    {
      if (!inputFile.eof())
      {
        std::getline(inputFile, line, '\n');
      }
      else
      {
        inputFile.close();
        return;
      }
    }


    double pointData;

    for (PointIdentifier pid = 0; pid < 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
VTKTetrahedralMeshReader<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
