/*=========================================================================
 *
 *  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.
 *
 *=========================================================================*/

#include "itkBYUMeshIO.h"

#include "itksys/SystemTools.hxx"

namespace itk
{
BYUMeshIO::BYUMeshIO()
  : m_PartId(NumericTraits<SizeValueType>::max())
  , m_FirstCellId(NumericTraits<SizeValueType>::OneValue())
  , m_LastCellId(NumericTraits<SizeValueType>::max())
{
  this->AddSupportedWriteExtension(".byu");
}

BYUMeshIO::~BYUMeshIO() = default;

bool
BYUMeshIO::CanReadFile(const char * fileName)
{
  if (!itksys::SystemTools::FileExists(fileName, true))
  {
    return false;
  }

  if (itksys::SystemTools::GetFilenameLastExtension(fileName) != ".byu")
  {
    return false;
  }

  return true;
}

bool
BYUMeshIO::CanWriteFile(const char * fileName)
{
  if (itksys::SystemTools::GetFilenameLastExtension(fileName) != ".byu")
  {
    return false;
  }

  return true;
}

void
BYUMeshIO::ReadMeshInformation()
{
  // Define input file stream and attach it to input file
  std::ifstream inputFile;

  // Due to the windows couldn't work well for tellg() and seekg() for ASCII mode, hence we
  // open the file with std::ios::binary
  inputFile.open(this->m_FileName.c_str(), std::ios::in | std::ios::binary);

  if (!inputFile.is_open())
  {
    itkExceptionMacro("Unable to open input file " << this->m_FileName);
  }

  // Read the ASCII file information
  unsigned int numberOfParts = 0;
  unsigned int numberOfConnectivityEntries = 0;

  // Read the number of points and number of cells
  inputFile >> numberOfParts;
  inputFile >> this->m_NumberOfPoints;
  inputFile >> this->m_NumberOfCells;
  inputFile >> numberOfConnectivityEntries;

  // Determine which part to read, default is to read all parts
  if (m_PartId > numberOfParts)
  {
    for (unsigned int ii = 0; ii < numberOfParts; ++ii)
    {
      inputFile >> m_FirstCellId >> m_LastCellId;
    }

    m_FirstCellId = 1;
    m_LastCellId = this->m_NumberOfCells;
  }
  else
  {
    unsigned int firstId;
    unsigned int lastId;
    for (unsigned int ii = 0; ii < m_PartId; ++ii)
    {
      inputFile >> firstId >> lastId;
    }

    inputFile >> m_FirstCellId;
    inputFile >> m_LastCellId;

    for (unsigned int ii = m_PartId + 1; ii < numberOfParts; ++ii)
    {
      inputFile >> firstId >> lastId;
    }
  }

  // Determine the start position of points
  m_FilePosition = inputFile.tellg();

  /** 6. Set default parameters */
  this->m_PointDimension = 3;
  this->m_FileType = IOFileEnum::ASCII;

  // If number of points is not equal zero, update points
  if (this->m_NumberOfPoints)
  {
    this->m_UpdatePoints = true;
  }

  // If number of cells is not equal zero, update points
  if (this->m_NumberOfCells)
  {
    this->m_UpdateCells = true;
  }

  // Set default point component type
  this->m_PointComponentType = IOComponentEnum::DOUBLE;

  // Read and omit points
  double x;
  for (SizeValueType ii = 0; ii < this->m_NumberOfPoints; ++ii)
  {
    for (unsigned int jj = 0; jj < this->m_PointDimension; ++jj)
    {
      inputFile >> x;
    }
  }

  // Determine cellbuffersize
  int ptId;
  this->m_CellBufferSize = 0;
  SizeValueType numLines = 0;
  while (numLines < this->m_NumberOfCells)
  {
    inputFile >> ptId;

    this->m_CellBufferSize++;
    if (ptId < 0)
    {
      ++numLines;
    }
  }

  // Set default cell component type
  this->m_CellComponentType = IOComponentEnum::UINT;
  this->m_CellBufferSize += this->m_NumberOfCells * 2;

  // Set default point pixel component and point pixel type
  this->m_PointPixelComponentType = IOComponentEnum::FLOAT;
  this->m_PointPixelType = IOPixelEnum::SCALAR;
  this->m_NumberOfPointPixelComponents = itk::NumericTraits<unsigned int>::OneValue();

  // Set default cell pixel component and point pixel type
  this->m_CellPixelComponentType = IOComponentEnum::FLOAT;
  this->m_CellPixelType = IOPixelEnum::SCALAR;
  this->m_NumberOfCellPixelComponents = itk::NumericTraits<unsigned int>::OneValue();

  inputFile.close();
}

void
BYUMeshIO::ReadPoints(void * buffer)
{
  // Define input file stream and attach it to input file
  std::ifstream inputFile;

  /** Due to the windows couldn't work well for tellg() and seekg() for ASCII mode, hence we
  open the file with std::ios::binary */
  inputFile.open(this->m_FileName.c_str(), std::ios::in | std::ios::binary);

  if (!inputFile.is_open())
  {
    itkExceptionMacro("Unable to open input file " << this->m_FileName);
  }

  // Set the position to points start
  inputFile.seekg(m_FilePosition, std::ios::beg);

  // Number of data array
  auto * data = static_cast<double *>(buffer);

  // Read points
  inputFile.precision(12);

  SizeValueType index = 0;
  for (SizeValueType id = 0; id < this->m_NumberOfPoints; ++id)
  {
    for (unsigned int ii = 0; ii < this->m_PointDimension; ++ii)
    {
      inputFile >> data[index++];
    }
  }

  // Determine cells start position
  m_FilePosition = inputFile.tellg();
  inputFile.close();
}

void
BYUMeshIO::ReadCells(void * buffer)
{
  // Define input file stream and attach it to input file
  std::ifstream inputFile;

  inputFile.open(this->m_FileName.c_str(), std::ios::in | std::ios::binary);

  if (!inputFile.is_open())
  {
    itkExceptionMacro("Unable to open input file " << this->m_FileName);
  }

  // Set the position to current position
  inputFile.seekg(m_FilePosition, std::ios::beg);

  // Get cell buffer
  inputFile.precision(12);
  auto *        data = static_cast<unsigned int *>(buffer);
  SizeValueType numPoints = 0;
  SizeValueType id{};
  SizeValueType index = 2;
  int           ptId;
  m_FirstCellId -= 1;
  m_LastCellId -= 1;
  while (id < this->m_NumberOfCells)
  {
    inputFile >> ptId;
    if (ptId >= 0)
    {
      if (id >= m_FirstCellId && id <= m_LastCellId)
      {
        data[index++] = ptId - 1;
        ++numPoints;
      }
    }
    else
    {
      if (id >= m_FirstCellId && id <= m_LastCellId)
      {
        data[index++] = -(ptId + 1);
        ++numPoints;
        data[index - numPoints - 2] = static_cast<unsigned int>(CellGeometryEnum::POLYGON_CELL);
        data[index - numPoints - 1] = numPoints;
        numPoints = 0;
        index += 2;
      }
      ++id;
    }
  }

  inputFile.close();
}

void
BYUMeshIO::ReadPointData(void * itkNotUsed(buffer))
{}

void
BYUMeshIO::ReadCellData(void * itkNotUsed(buffer))
{}

void
BYUMeshIO::WriteMeshInformation()
{
  // Check file name
  if (this->m_FileName.empty())
  {
    itkExceptionMacro("No Input FileName");
  }

  // Write to output file
  std::ofstream outputFile(this->m_FileName.c_str(), std::ios::out);

  if (!outputFile.is_open())
  {
    itkExceptionMacro("Unable to open file\n"
                      "outputFilename= "
                      << this->m_FileName);
  }

  // Write BYU file header
  Indent indent(7);
  outputFile << indent << 1 << indent << this->m_NumberOfPoints << indent << this->m_NumberOfCells << indent
             << this->m_CellBufferSize - 2 * this->m_NumberOfCells << std::endl
             << indent << 1 << indent << this->m_NumberOfCells << std::endl;

  outputFile.close();
}

void
BYUMeshIO::WritePoints(void * buffer)
{
  // check file name
  if (this->m_FileName.empty())
  {
    itkExceptionMacro("No Input FileName");
  }

  // Write to output file
  std::ofstream outputFile(this->m_FileName.c_str(), std::ios_base::app);

  if (!outputFile.is_open())
  {
    itkExceptionMacro("Unable to open file\n"
                      "outputFilename= "
                      << this->m_FileName);
  }

  // Write points
  switch (this->m_PointComponentType)
  {
    case IOComponentEnum::UCHAR:
    {
      WritePoints(static_cast<unsigned char *>(buffer), outputFile);
      break;
    }
    case IOComponentEnum::CHAR:
    {
      WritePoints(static_cast<char *>(buffer), outputFile);

      break;
    }
    case IOComponentEnum::USHORT:
    {
      WritePoints(static_cast<unsigned short *>(buffer), outputFile);

      break;
    }
    case IOComponentEnum::SHORT:
    {
      WritePoints(static_cast<short *>(buffer), outputFile);

      break;
    }
    case IOComponentEnum::UINT:
    {
      WritePoints(static_cast<unsigned int *>(buffer), outputFile);

      break;
    }
    case IOComponentEnum::INT:
    {
      WritePoints(static_cast<int *>(buffer), outputFile);

      break;
    }
    case IOComponentEnum::ULONG:
    {
      WritePoints(static_cast<unsigned long *>(buffer), outputFile);

      break;
    }
    case IOComponentEnum::LONG:
    {
      WritePoints(static_cast<long *>(buffer), outputFile);

      break;
    }
    case IOComponentEnum::ULONGLONG:
    {
      WritePoints(static_cast<unsigned long long *>(buffer), outputFile);

      break;
    }
    case IOComponentEnum::LONGLONG:
    {
      WritePoints(static_cast<long long *>(buffer), outputFile);

      break;
    }
    case IOComponentEnum::FLOAT:
    {
      WritePoints(static_cast<float *>(buffer), outputFile);

      break;
    }
    case IOComponentEnum::DOUBLE:
    {
      WritePoints(static_cast<double *>(buffer), outputFile);

      break;
    }
    case IOComponentEnum::LDOUBLE:
    {
      WritePoints(static_cast<long double *>(buffer), outputFile);

      break;
    }
    default:
    {
      itkExceptionMacro("Unknown point pixel component type" << std::endl);
    }
  }

  outputFile.close();
}

void
BYUMeshIO::WriteCells(void * buffer)
{
  // Check file name
  if (this->m_FileName.empty())
  {
    itkExceptionMacro("No Input FileName");
  }

  // Write to output file
  std::ofstream outputFile(this->m_FileName.c_str(), std::ios_base::app);

  if (!outputFile.is_open())
  {
    itkExceptionMacro("Unable to open file\n"
                      "outputFilename= "
                      << this->m_FileName);
  }

  // Write polygons
  switch (this->m_CellComponentType)
  {
    case IOComponentEnum::UCHAR:
    {
      WriteCells(static_cast<unsigned char *>(buffer), outputFile);
      break;
    }
    case IOComponentEnum::CHAR:
    {
      WriteCells(static_cast<unsigned char *>(buffer), outputFile);
      break;
    }
    case IOComponentEnum::USHORT:
    {
      WriteCells(static_cast<unsigned short *>(buffer), outputFile);
      break;
    }
    case IOComponentEnum::SHORT:
    {
      WriteCells(static_cast<short *>(buffer), outputFile);
      break;
    }
    case IOComponentEnum::UINT:
    {
      WriteCells(static_cast<unsigned int *>(buffer), outputFile);
      break;
    }
    case IOComponentEnum::INT:
    {
      WriteCells(static_cast<int *>(buffer), outputFile);
      break;
    }
    case IOComponentEnum::ULONG:
    {
      WriteCells(static_cast<unsigned long *>(buffer), outputFile);
      break;
    }
    case IOComponentEnum::LONG:
    {
      WriteCells(static_cast<long *>(buffer), outputFile);
      break;
    }
    case IOComponentEnum::ULONGLONG:
    {
      WriteCells(static_cast<unsigned long long *>(buffer), outputFile);
      break;
    }
    case IOComponentEnum::LONGLONG:
    {
      WriteCells(static_cast<long long *>(buffer), outputFile);
      break;
    }
    case IOComponentEnum::FLOAT:
    {
      WriteCells(static_cast<float *>(buffer), outputFile);
      break;
    }
    case IOComponentEnum::DOUBLE:
    {
      WriteCells(static_cast<double *>(buffer), outputFile);
      break;
    }
    case IOComponentEnum::LDOUBLE:
    {
      WriteCells(static_cast<long double *>(buffer), outputFile);
      break;
    }
    default:
    {
      itkExceptionMacro("Unknown cell pixel component type" << std::endl);
    }
  }

  outputFile.close();
}

void
BYUMeshIO::WritePointData(void * itkNotUsed(buffer))
{}

void
BYUMeshIO::WriteCellData(void * itkNotUsed(buffer))
{}

void
BYUMeshIO::Write()
{}

void
BYUMeshIO::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);

  os << indent << "FilePosition: " << static_cast<typename NumericTraits<StreamOffsetType>::PrintType>(m_FilePosition)
     << std::endl;
  os << indent << "PartId: " << m_PartId << std::endl;
  os << indent << "First Cell Id: " << m_FirstCellId << std::endl;
  os << indent << "Last Cell Id: " << m_LastCellId << std::endl;
}
} // namespace itk
