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

  Program:   Visualization Toolkit
  Module:    vtkBMPReader.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/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 notice for more information.

=========================================================================*/
#include "vtkBMPReader.h"

#include "vtkByteSwap.h"
#include "vtkImageData.h"
#include "vtkLookupTable.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include <vtksys/SystemTools.hxx>

vtkStandardNewMacro(vtkBMPReader);

#ifdef read
#undef read
#endif

#ifdef close
#undef close
#endif

vtkBMPReader::vtkBMPReader()
{
  this->Colors = nullptr;
  this->SetDataByteOrderToLittleEndian();
  this->Depth = 0;
  // we need to create it now in case its asked for later (pointer must be valid)
  this->LookupTable = vtkLookupTable::New();
  this->Allow8BitBMP = 0;
}

//------------------------------------------------------------------------------
vtkBMPReader::~vtkBMPReader()
{
  // free any old memory
  delete[] this->Colors;
  this->Colors = nullptr;

  if (this->LookupTable)
  {
    this->LookupTable->Delete();
    this->LookupTable = nullptr;
  }
}

//------------------------------------------------------------------------------
void vtkBMPReader::ExecuteInformation()
{
  int xsize, ysize;
  FILE* fp;
  vtkTypeInt32 tmp;
  vtkTypeInt32 offset;
  vtkTypeInt32 infoSize;
  vtkTypeInt16 stmp1, stmp2;

  // free any old memory
  delete[] this->Colors;
  this->Colors = nullptr;

  // 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];
  }

  this->ComputeInternalFileName(this->DataExtent[4]);
  if (this->InternalFileName == nullptr || this->InternalFileName[0] == '\0')
  {
    return;
  }
  // 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;
  }

  // compare magic number to determine file type
  if ((fgetc(fp) != 'B') || (fgetc(fp) != 'M'))
  {
    vtkErrorMacro(<< "Unknown file type! " << this->InternalFileName
                  << " is not a Windows BMP file!");
    fclose(fp);
    return;
  }

  // error indicator
  bool errorOccurred = false;

  // get the size of the file

  // skip 4 bytes
  if (fread(&tmp, 4, 1, fp) != 1)
  {
    errorOccurred = true;
  }
  // skip 4 more bytes
  else if (fread(&tmp, 4, 1, fp) != 1)
  {
    errorOccurred = true;
  }
  // read the offset
  else if (fread(&offset, 4, 1, fp) != 1)
  {
    errorOccurred = true;
  }
  // get size of header
  else if (fread(&infoSize, 4, 1, fp) != 1)
  {
    errorOccurred = true;
  }

  if (errorOccurred)
  {
    vtkErrorMacro("Error reading file: " << this->InternalFileName << " Premature end of file.");
    fclose(fp);
    return;
  }

  vtkByteSwap::Swap4LE(&infoSize);

  // error checking
  if ((infoSize != 40) && (infoSize != 12))
  {
    vtkErrorMacro("Unknown file type! " << this->InternalFileName << " is not a Windows BMP file!");
    fclose(fp);
    return;
  }

  // there are two different types of BMP files
  if (infoSize == 40)
  {
    // now get the dimensions
    if (fread(&xsize, 4, 1, fp) != 1)
    {
      errorOccurred = true;
    }
    else if (fread(&ysize, 4, 1, fp) != 1)
    {
      errorOccurred = true;
    }
    vtkByteSwap::Swap4LE(&xsize);
    vtkByteSwap::Swap4LE(&ysize);
  }
  else
  {
    if (fread(&stmp1, 2, 1, fp) != 1)
    {
      errorOccurred = true;
    }
    else if (fread(&stmp2, 2, 1, fp) != 1)
    {
      errorOccurred = true;
    }
    vtkByteSwap::Swap2LE(&stmp1);
    vtkByteSwap::Swap2LE(&stmp2);
    xsize = stmp1;
    ysize = stmp2;
  }

  if (errorOccurred)
  {
    vtkErrorMacro("BMPReader error reading file: " << this->InternalFileName
                                                   << " Premature EOF while reading size.");
    fclose(fp);
    return;
  }

  // is corner in upper left or lower left
  if (ysize < 0)
  {
    ysize = ysize * -1;
    this->FileLowerLeft = 0;
  }
  else
  {
    this->FileLowerLeft = 1;
  }

  // ignore planes
  if (fread(&stmp1, 2, 1, fp) != 1)
  {
    errorOccurred = true;
  }
  // read depth
  else if (fread(&stmp2, 2, 1, fp) != 1)
  {
    errorOccurred = true;
  }

  if (errorOccurred)
  {
    vtkErrorMacro("BMPReader error reading file: " << this->InternalFileName
                                                   << " Premature EOF while reading depth.");
    fclose(fp);
    return;
  }

  vtkByteSwap::Swap2LE(&stmp2);
  this->Depth = stmp2;

  if ((this->Depth != 8) && (this->Depth != 24))
  {
    vtkErrorMacro("Only BMP depths of (8,24) are supported. Not " << this->Depth);
    fclose(fp);
    return;
  }

  // skip over rest of info for long format
  if (infoSize == 40)
  {
    int skip[6];
    if (fread(&skip, 4, 6, fp) != 6)
    {
      vtkErrorMacro("BMPReader error reading file: "
        << this->InternalFileName << " Premature EOF skipping rest of info for long format.");
      fclose(fp);
      return;
    }
  }

  // read in color table if required
  if (this->Depth < 24)
  {
    int numColors = 256;
    this->Colors = new unsigned char[numColors * 3];
    for (tmp = 0; tmp < numColors; tmp++)
    {
      this->Colors[tmp * 3 + 2] = fgetc(fp);
      this->Colors[tmp * 3 + 1] = fgetc(fp);
      this->Colors[tmp * 3] = fgetc(fp);
      if (infoSize == 40)
      {
        fgetc(fp);
      }
    }
    if (this->Allow8BitBMP)
    {
      if (!this->LookupTable)
      {
        this->LookupTable = vtkLookupTable::New();
      }
      this->LookupTable->SetNumberOfTableValues(numColors);
      for (tmp = 0; tmp < numColors; tmp++)
      {
        this->LookupTable->SetTableValue(tmp, this->Colors[tmp * 3 + 0] / 255.0,
          this->Colors[tmp * 3 + 1] / 255.0, this->Colors[tmp * 3 + 2] / 255.0, 1);
      }
      this->LookupTable->SetRange(0, 255);
    }
  }

  if (fclose(fp))
  {
    vtkWarningMacro("File close failed on " << this->InternalFileName);
  }

  // Offset is the true header size. See bug 14397
  vtkByteSwap::Swap4LE(&offset);
  this->ManualHeaderSize = 1;
  this->HeaderSize = offset;

  // 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();
  if ((this->Depth == 8) && this->Allow8BitBMP)
  {
    this->SetNumberOfScalarComponents(1);
  }
  else
  {
    this->SetNumberOfScalarComponents(3);
  }

  this->vtkImageReader::ExecuteInformation();
}

//------------------------------------------------------------------------------
// This function opens a file to determine the file size, and to
// automatically determine the header size.
void vtkBMPReader::ComputeDataIncrements()
{
  int idx;
  vtkIdType fileDataLength;

  // Determine the expected length of the data ...
  switch (this->DataScalarType)
  {
    case VTK_FLOAT:
      fileDataLength = sizeof(float);
      break;
    case VTK_INT:
      fileDataLength = sizeof(int);
      break;
    case VTK_SHORT:
      fileDataLength = sizeof(short);
      break;
    case VTK_UNSIGNED_SHORT:
      fileDataLength = sizeof(unsigned short);
      break;
    case VTK_UNSIGNED_CHAR:
      fileDataLength = sizeof(unsigned char);
      break;
    default:
      vtkErrorMacro(<< "Unknown DataScalarType");
      return;
  }

  fileDataLength *= (this->Depth / 8);

  // a row must end on a 4 byte boundary
  // so update the Increments[1]
  this->DataIncrements[0] = fileDataLength;
  fileDataLength = fileDataLength * (this->DataExtent[1] - this->DataExtent[0] + 1);
  // move to 4 byte boundary
  fileDataLength = fileDataLength + (4 - fileDataLength % 4) % 4;

  // compute the fileDataLength (in units of bytes)
  for (idx = 1; idx < 3; ++idx)
  {
    this->DataIncrements[idx] = fileDataLength;
    fileDataLength =
      fileDataLength * (this->DataExtent[idx * 2 + 1] - this->DataExtent[idx * 2] + 1);
  }
}

//------------------------------------------------------------------------------
// This function reads in one data of data.
// templated to handle different data types.
template <class OT>
void vtkBMPReaderUpdate2(vtkBMPReader* self, vtkImageData* data, OT* outPtr)
{
  vtkIdType inIncr[3], outIncr[3];
  OT *outPtr0, *outPtr1, *outPtr2;
  vtkIdType streamSkip0, streamSkip1;
  vtkIdType streamRead;
  int idx0, idx1, idx2, pixelRead;
  int inExtent[6];
  int dataExtent[6];
  int pixelSkip;
  unsigned char* Colors;
  unsigned long count = 0;
  unsigned long target;
  int Keep8bit = 0;

  // Get the requested extents.
  data->GetExtent(inExtent);
  // Convert them into to the extent needed from the file.
  self->ComputeInverseTransformedExtent(inExtent, dataExtent);

  // get and transform the increments
  data->GetIncrements(inIncr);
  self->ComputeInverseTransformedIncrements(inIncr, outIncr);

  // get the color lut
  Colors = self->GetColors();

  // are we converting to RGB or staying as 8bit
  if ((self->GetDepth() == 8) && self->GetAllow8BitBMP())
  {
    Keep8bit = 1;
  }

  // compute outPtr2
  outPtr2 = outPtr;
  if (outIncr[0] < 0)
  {
    outPtr2 = outPtr2 - outIncr[0] * (dataExtent[1] - dataExtent[0]);
  }
  if (outIncr[1] < 0)
  {
    outPtr2 = outPtr2 - outIncr[1] * (dataExtent[3] - dataExtent[2]);
  }
  if (outIncr[2] < 0)
  {
    outPtr2 = outPtr2 - outIncr[2] * (dataExtent[5] - dataExtent[4]);
  }

  // length of a row, num pixels read at a time
  pixelRead = dataExtent[1] - dataExtent[0] + 1;
  streamRead = (vtkIdType)(pixelRead * self->GetDataIncrements()[0]);
  streamSkip0 = (vtkIdType)(self->GetDataIncrements()[1] - streamRead);
  streamSkip1 = (vtkIdType)(self->GetDataIncrements()[2] -
    (dataExtent[3] - dataExtent[2] + 1) * self->GetDataIncrements()[1]);
  pixelSkip = self->GetDepth() / 8;

  // read from the bottom up
  if (!self->GetFileLowerLeft())
  {
    streamSkip0 = (vtkIdType)(-streamRead - self->GetDataIncrements()[1]);
  }

  target = (unsigned long)((dataExtent[5] - dataExtent[4] + 1) *
    (dataExtent[3] - dataExtent[2] + 1) / 50.0);
  target++;

  if (self->GetFileDimensionality() == 3)
  {
    if (!self->OpenAndSeekFile(dataExtent, 0))
    {
      return;
    }
  }

  // create a buffer to hold a row of the data
  std::vector<unsigned char> buf(streamRead);

  // read the data row by row
  for (idx2 = dataExtent[4]; idx2 <= dataExtent[5]; ++idx2)
  {
    if (self->GetFileDimensionality() == 2)
    {
      if (!self->OpenAndSeekFile(dataExtent, idx2))
      {
        return;
      }
    }
    outPtr1 = outPtr2;
    for (idx1 = dataExtent[2]; !self->AbortExecute && idx1 <= dataExtent[3]; ++idx1)
    {
      if (!(count % target))
      {
        self->UpdateProgress(count / (50.0 * target));
      }
      count++;
      outPtr0 = outPtr1;

      // read the row.
      if (!self->GetFile()->read(reinterpret_cast<char*>(buf.data()), streamRead))
      {
        vtkErrorWithObjectMacro(self,
          "File operation failed. row = "
            << idx1 << ", Read = " << streamRead << ", Skip0 = " << streamSkip0 << ", Skip1 = "
            << streamSkip1 << ", FilePos = " << static_cast<vtkIdType>(self->GetFile()->tellg())
            << ", FileName = " << self->GetInternalFileName());
        self->CloseFile();
        return;
      }

      // copy the bytes into the typed data
      auto inPtr = buf.cbegin();
      for (idx0 = dataExtent[0]; idx0 <= dataExtent[1]; ++idx0)
      {
        // Copy pixel into the output.
        if (self->GetDepth() == 8 && !Keep8bit)
        {
          outPtr0[0] = (OT)(Colors[inPtr[0] * 3]);
          outPtr0[1] = (OT)(Colors[inPtr[0] * 3 + 1]);
          outPtr0[2] = (OT)(Colors[inPtr[0] * 3 + 2]);
        }
        else if (self->GetDepth() == 8 && Keep8bit)
        {
          outPtr0[0] = (OT)(inPtr[0]);
        }
        else
        {
          outPtr0[0] = (OT)(inPtr[2]);
          outPtr0[1] = (OT)(inPtr[1]);
          outPtr0[2] = (OT)(inPtr[0]);
        }
        // move to next pixel
        inPtr += pixelSkip;
        outPtr0 += outIncr[0];
      }
      // move to the next row in the file and data
      self->GetFile()->seekg(static_cast<long>(self->GetFile()->tellg()) + streamSkip0, ios::beg);
      outPtr1 += outIncr[1];
    }
    // move to the next image in the file and data
    self->GetFile()->seekg(static_cast<long>(self->GetFile()->tellg()) + streamSkip1, ios::beg);
    outPtr2 += outIncr[2];
  }

  self->CloseFile();

  // delete the temporary buffer
}

//------------------------------------------------------------------------------
// This function reads a data from a file.  The datas extent/axes
// are assumed to be the same as the file extent/order.
void vtkBMPReader::ExecuteDataWithInformation(vtkDataObject* output, vtkInformation* outInfo)
{
  vtkImageData* data = this->AllocateOutputData(output, outInfo);

  if (this->UpdateExtentIsEmpty(outInfo, output))
  {
    return;
  }
  if (this->InternalFileName == nullptr)
  {
    vtkErrorMacro(<< "Either a FileName or FilePrefix must be specified.");
    return;
  }

  data->GetPointData()->GetScalars()->SetName("BMPImage");

  this->ComputeDataIncrements();

  // Call the correct templated function for the output
  void* outPtr;

  // Call the correct templated function for the input
  outPtr = data->GetScalarPointer();
  switch (data->GetScalarType())
  {
    vtkTemplateMacro(vtkBMPReaderUpdate2(this, data, static_cast<VTK_TT*>(outPtr)));
    default:
      vtkErrorMacro(<< "Execute: Unknown data type");
  }
}

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

  // this->Colors is not printed
  os << indent << "Depth: " << this->Depth << "\n";
  os << indent << "Allow8BitBMP: " << this->Allow8BitBMP << "\n";
  if (this->LookupTable)
  {
    os << indent << "LookupTable: " << this->LookupTable << "\n";
  }
  else
  {
    os << indent << "LookupTable: nullptr\n";
  }
}

//------------------------------------------------------------------------------
int vtkBMPReader::CanReadFile(const char* fname)
{
  // get the magic number by reading in a file
  FILE* fp = vtksys::SystemTools::Fopen(fname, "rb");
  if (!fp)
  {
    return 0;
  }

  // compare magic number to determine file type
  if ((fgetc(fp) != 'B') || (fgetc(fp) != 'M'))
  {
    fclose(fp);
    return 0;
  }

  vtkTypeInt32 tmp;
  vtkTypeInt32 infoSize = 0;

  // error indicator
  bool errorOccurred = false;

  // skip 4 bytes
  if (fread(&tmp, 4, 1, fp) != 1)
  {
    errorOccurred = true;
  }
  // skip 4 more bytes
  else if (fread(&tmp, 4, 1, fp) != 1)
  {
    errorOccurred = true;
  }
  // read the offset
  else if (fread(&tmp, 4, 1, fp) != 1)
  {
    errorOccurred = true;
  }
  // get size of header
  else if (fread(&infoSize, 4, 1, fp) != 1)
  {
    infoSize = 0;
    errorOccurred = true;
  }

  vtkByteSwap::Swap4LE(&infoSize);

  // error checking
  if ((infoSize != 40) && (infoSize != 12))
  {
    errorOccurred = true;
  }

  fclose(fp);
  return !errorOccurred;
}
