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

#include "vtkImageData.h"
#include "vtkObjectFactory.h"
#include "vtkStreamingDemandDrivenPipeline.h"
#include <vtksys/SystemTools.hxx>

VTK_ABI_NAMESPACE_BEGIN
vtkStandardNewMacro(vtkPNMReader);

static char vtkPNMReaderGetChar(FILE* fp)
{
  char c;
  int result;

  if ((result = getc(fp)) == EOF)
  {
    return '\0';
  }

  c = (char)result;
  if (c == '#')
  {
    do
    {
      if ((result = getc(fp)) == EOF)
      {
        return '\0';
      }
      c = (char)result;
    } while (c != '\n');
  }

  return c;
}

static int vtkPNMReaderGetInt(FILE* fp)
{
  char c;
  int result = 0;

  do
  {
    c = vtkPNMReaderGetChar(fp);
  } while ((c < '1') || (c > '9'));
  do
  {
    result = result * 10 + (c - '0');
    c = vtkPNMReaderGetChar(fp);
  } while ((c >= '0') && (c <= '9'));

  // put the CR/LF or whitespace back.....
  ungetc(c, fp);
  return result;
}

void vtkPNMReader::ExecuteInformation()
{
  int xsize, ysize, comp;
  char magic[80];
  char c;
  FILE* fp;

  // if the user has not set the extent, but has set the VOI
  // set the zaxis extent to the VOI z axis
  if (this->DataExtent[4] == 0 && this->DataExtent[5] == 0 &&
    (this->DataVOI[4] || this->DataVOI[5]))
  {
    this->DataExtent[4] = this->DataVOI[4];
    this->DataExtent[5] = this->DataVOI[5];
  }

  if (!this->FileName && !this->FilePattern)
  {
    vtkErrorMacro(<< "Either a FileName or FilePattern must be specified.");
    return;
  }

  // Allocate the space for the filename
  this->ComputeInternalFileName(this->DataExtent[4]);

  // get the magic number by reading in a file
  fp = vtksys::SystemTools::Fopen(this->InternalFileName, "rb");
  if (!fp)
  {
    vtkErrorMacro("Unable to open file " << this->InternalFileName);
    return;
  }

  do
  {
    c = vtkPNMReaderGetChar(fp);
    if (c == '\0')
    { // Bad file.
      int invalidExtent[6] = { 0, -1, 0, -1, 0, -1 };
      vtkStreamingDemandDrivenPipeline::SetWholeExtent(
        this->GetOutputInformation(0), invalidExtent);
      fclose(fp);
      return;
    }
  } while (c != 'P');
  magic[0] = c;
  magic[1] = vtkPNMReaderGetChar(fp);
  magic[2] = '\0';

  // now get the dimensions
  xsize = vtkPNMReaderGetInt(fp);
  ysize = vtkPNMReaderGetInt(fp);

  // read max pixel value into comp for now
  vtkPNMReaderGetInt(fp);
  // if file is ascii, any amount of whitespace may follow.
  // if file is binary, a single whitespace character will follow.
  // We only support binary ppm and pgm files right now.  So the next
  // character IS always ignored.
  c = getc(fp);

  // if this file was "written" on the PC, then a CR will have been
  // written as a CR/LF combination.  So, if this single whitespace
  // character is a CR and it is followed by a LF, then swallow the
  // linefeed character as well. (Not part of the PPM standard, but a
  // a hard fact of life.
  if (c == 0x0d)
  {
    c = getc(fp);
    if (c != 0x0a)
    {
      ungetc(c, fp);
    }
  }

  // Set the header size now that we have parsed it
  this->SetHeaderSize(ftell(fp));

  fclose(fp);

  // compare magic number to determine file type
  if (!strcmp(magic, "P5"))
  {
    comp = 1;
  }
  else if (!strcmp(magic, "P6"))
  {
    comp = 3;
  }
  else
  {
    vtkErrorMacro(<< "Unknown file type! " << this->InternalFileName
                  << " is not a binary PGM or PPM!");
    return;
  }

  // if the user has set the VOI, just make sure its valid
  if (this->DataVOI[0] || this->DataVOI[1] || this->DataVOI[2] || this->DataVOI[3] ||
    this->DataVOI[4] || this->DataVOI[5])
  {
    if ((this->DataVOI[0] < 0) || (this->DataVOI[1] >= xsize) || (this->DataVOI[2] < 0) ||
      (this->DataVOI[3] >= ysize))
    {
      vtkWarningMacro(
        "The requested VOI is larger than the file's (" << this->InternalFileName << ") extent ");
      this->DataVOI[0] = 0;
      this->DataVOI[1] = xsize - 1;
      this->DataVOI[2] = 0;
      this->DataVOI[3] = ysize - 1;
    }
  }

  this->DataExtent[0] = 0;
  this->DataExtent[1] = xsize - 1;
  this->DataExtent[2] = 0;
  this->DataExtent[3] = ysize - 1;

  this->SetDataScalarTypeToUnsignedChar();
  this->SetNumberOfScalarComponents(comp);

  this->vtkImageReader::ExecuteInformation();
}

inline int iseol(int c)
{
  return c == 10 || c == 13;
}

int vtkPNMReader::CanReadFile(const char* fname)
{
  FILE* fp = vtksys::SystemTools::Fopen(fname, "rb");
  if (!fp)
  {
    return 0;
  }
  unsigned char magic[3];
  if (fread(magic, 1, 3, fp) != 3)
  {
    fclose(fp);
    return 0;
  }
  int ok = ((magic[0] == 'P') && iseol(magic[2]) && (magic[1] >= '1' && magic[1] <= '6'));
  fclose(fp);
  if (ok)
  {
    return 3;
  }
  return 0;
}

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