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

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/VolViewCopyright.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 "vtkLSMReader.h"

#include "vtkObjectFactory.h"
#include "vtkByteSwap.h"
#include "vtkImageData.h"

#include <stdio.h>
#include <sys/stat.h>

#if(!defined(TIFFTAG_CZ_LSMINFO))
  #define TIFFTAG_CZ_LSMINFO 34412
#endif

//----------------------------------------------------------------------------
vtkStandardNewMacro(vtkLSMReader);
vtkCxxRevisionMacro(vtkLSMReader, "$Revision: 1.13 $");

extern "C" {
  void vtkLSMReaderInternalErrorHandler(const char* vtkNotUsed(module), 
                                        const char* vtkNotUsed(fmt), 
                                        va_list vtkNotUsed(ap))
  {
    // Do nothing
    // Ignore errors
  }
}

vtkLSMReader::vtkLSMReader() 
{
  this->Image = 0;

  this->OriginSpecifiedFlag = false;
  this->SpacingSpecifiedFlag = false;
}

vtkLSMReader::~vtkLSMReader() 
{
  this->Clean();
}
 
int vtkLSMReader::Open( const char *filename )
{
  this->Clean();
  struct stat fs;
  if ( stat(filename, &fs) )
    {
    return 0;
    }
  this->Image = TIFFOpen(filename, "r");
  if ( !this->Image)
    {
    this->Clean();
    return 0;
    }
  
  TIFFSetErrorHandler(&vtkLSMReaderInternalErrorHandler);
  TIFFSetWarningHandler(&vtkLSMReaderInternalErrorHandler);

  return 1;
}

void vtkLSMReader::Clean()
{
  if ( this->Image )
    {
    TIFFClose(this->Image);
    }
  this->Image=NULL;
}

int vtkLSMReader::CanReadFile(const char* fname)
{
  int res = this->Open(fname);
  if (!res || !this->Image)
    {
    this->Clean();
    return 0;
    }
  
  TIFFSetDirectory(this->Image,0);
  unsigned long size;
  unsigned char *tmp;
  // look for the zeiss structure
  if ( !TIFFGetField(this->Image, TIFFTAG_CZ_LSMINFO, &size, &tmp))
    {
    return 0;
    }
  
  this->Clean();
  return 3;
}

void vtkLSMReader::ExecuteInformation()
{
  this->ComputeInternalFileName(this->DataExtent[4]);
  if (this->InternalFileName == NULL)
    {
    return;
    }
  
  if ( !this->Open(this->InternalFileName) || !this->Image)
    {  
    vtkErrorMacro("Unable to open file " <<this->InternalFileName );
    this->DataExtent[0] = 0;
    this->DataExtent[1] = 0;
    this->DataExtent[2] = 0;
    this->DataExtent[3] = 0;
    this->DataExtent[4] = 0;
    this->DataExtent[5] = 0;
    this->SetNumberOfScalarComponents(1);
    this->vtkImageReader2::ExecuteInformation();
    return;
    }

  int dircount = 0;
  int width, height;
  short samplesPerPixel, bitsPerSample[3];
  unsigned int compression, photometric, planarConfig;
  do
    {
    dircount++;
    }
  while( TIFFReadDirectory( this->Image ));
  TIFFSetDirectory(this->Image,0);
  
  // read in the zeiss structure
  unsigned long size;
  unsigned char *czAddr;
  if ( !TIFFGetField(this->Image, TIFFTAG_CZ_LSMINFO, &size, &czAddr))
    {
    vtkErrorMacro("Not a valid Zeiss LSM file: " <<this->InternalFileName );
    this->Clean();
    return;
    }
  // read values from the resulting structure
  //verify the magic number
  int magic;
  memcpy(&magic,czAddr,4);
  vtkByteSwap::Swap4LE(&magic);
  if (magic != 0x0400494C)
    {
    vtkErrorMacro("This is not a valid Zeiss version 1.5 or later LSM file: " <<
                  this->InternalFileName );
    this->Clean();
    return;    
    }
  
  // get other info from the structure
  // how many slices are there
  int slices;
  memcpy(&slices,czAddr+16,4);
  vtkByteSwap::Swap4LE(&slices);
  // pull out the width/height, etc.
  if ( !TIFFGetField(this->Image, TIFFTAG_IMAGEWIDTH, &width) ||
       !TIFFGetField(this->Image, TIFFTAG_IMAGELENGTH, &height) )
    {
    vtkErrorMacro("This is not a valid Zeiss version 1.5 or later LSM file: " <<
                  this->InternalFileName );
    this->Clean();
    return;
    }
  this->DataExtent[0] = 0;
  this->DataExtent[1] = width - 1;
  this->DataExtent[2] = 0;
  this->DataExtent[3] = height - 1;
  this->DataExtent[4] = 0;
  this->DataExtent[5] = slices - 1;
  
  // what is the spacing
  double spacing[3];
  int i;
  for (i = 0; i < 3; ++i)
    {
    memcpy(spacing+i,czAddr+40+i*8,8);
    vtkByteSwap::Swap8LE(spacing+i);
    }
  // convert spacing from meters to micro µm
  if( !SpacingSpecifiedFlag )
    {  
    this->SetDataSpacing(spacing[0]*1000000.0,
                         spacing[1]*1000000.0,
                         spacing[2]*1000000.0);
    }

  // what is the scan type (only support regular x-y-z scans)
  unsigned int scanType;
  memcpy(&scanType,czAddr+64,4);
  vtkByteSwap::Swap4LE(&scanType);
  if (scanType != 0)
    {
    vtkErrorMacro("Unsupported scan type of : " << scanType << " in file " <<
                  this->InternalFileName );
    this->Clean();
    return;    
    }
  
  // what is the data type
  int dataType;
  memcpy(&dataType,czAddr+28,4);
  vtkByteSwap::Swap4LE(&dataType);
  switch (dataType)
    {
    case 1:
      this->SetDataScalarTypeToUnsignedChar();
      break;
    case 2:
      this->SetDataScalarTypeToUnsignedShort();
      break;
    case 5:
    case 0:
    default:
      vtkErrorMacro("Unsupported data type of : " << dataType << " in file " <<
                    this->InternalFileName );
      this->Clean();
      return;    
    }
  
  // TODO in the future read in more info from these crazy structures
  // do we have channel info
  //unsigned int scanOffset;
  // memcpy(&scanOffset,czAddr+84,4);
  //vtkByteSwap::Swap4LE(&scanOffset);
  
  TIFFGetField(this->Image, TIFFTAG_SAMPLESPERPIXEL,&samplesPerPixel);
  if (samplesPerPixel > 4)
    {
    vtkErrorMacro("VolView only supports up to four channels and this file contains " << samplesPerPixel << " channels. VolView will read in the first four channels only.");
    samplesPerPixel = 4;
    }
  this->SetNumberOfScalarComponents(samplesPerPixel);
  
  TIFFGetField(this->Image, TIFFTAG_COMPRESSION, &compression);
  TIFFGetField(this->Image, TIFFTAG_PHOTOMETRIC, &photometric);

  // the following should be redundent since we already read in the data type
  // form the CZ structure
  TIFFGetField(this->Image, TIFFTAG_BITSPERSAMPLE, bitsPerSample);
  switch (bitsPerSample[0])
    {
    case 8:
      this->SetDataScalarTypeToUnsignedChar();
      break;
    case 16:
      this->SetDataScalarTypeToUnsignedShort();
      break;
    default:
      vtkErrorMacro(
        "VolView does not support Zeiss Timeseries Mean-of-ROI datasets.");
      this->Clean();
      return;
    }
  
  // planar config is always 2 for LSM
  planarConfig = 2;
  //TIFFGetField(this->Image, TIFFTAG_PLANARCONFIG, &planarConfig);

  
  this->vtkImageReader2::ExecuteInformation();

  // close the file
  this->Clean();
}

template <class OT>
void vtkLSMReaderUpdate(vtkLSMReader *self, vtkImageData *data, OT *outPtr, TIFF *Image)
{
  vtkIdType outIncr[3];
  int outExtent[6];
  OT *outPtr2;
  
  data->GetExtent(outExtent);
  data->GetIncrements(outIncr);
  int *wExt = data->GetWholeExtent();
  
  unsigned int numComps = data->GetNumberOfScalarComponents();
  unsigned int comp;
  int bytes;
    
  // we must get to the correct directory
  int numImageDirectory = -1;
  int currentDirectory = -1;
  unsigned int subFileType;
  uint8 *tmpStrip;
  unsigned int numPixels, pixel;
  
  numPixels = (outExtent[1] - outExtent[0] + 1)*
    (outExtent[3] - outExtent[2] + 1);
  
  tmpStrip = new unsigned char 
    [sizeof(OT)*(wExt[1] - wExt[0] + 1)*(wExt[3] - wExt[2] + 1)];

  outPtr2 = outPtr;
  int idx2;
  for (idx2 = outExtent[4]; idx2 <= outExtent[5]; ++idx2)
    {
    // read in a slice and reassemble as neccesary
    // we must find the correct directory for this slice
    while (numImageDirectory < idx2)
      {
      currentDirectory++;
      TIFFSetDirectory(Image,currentDirectory);
      // is this an image directory
      TIFFGetField(Image, TIFFTAG_SUBFILETYPE,&subFileType);
      if (subFileType == 0)
        {
        numImageDirectory++;
        }
      }
    
    // OK now we are on the correct directory, read in the data
    int currStrip = 0;
    for (comp = 0; comp < numComps; ++comp)
      {
      unsigned int bytesRead = 0;
      while (bytesRead < numPixels*sizeof(OT))
        {
        bytes = TIFFReadEncodedStrip(Image, currStrip, 
                                     tmpStrip+bytesRead, 
                                     numPixels*sizeof(OT) - bytesRead);
        if( bytes == -1 )
          {
          vtkGenericWarningMacro("Read Failure in LSM Reader");
          return;
          }
        bytesRead += bytes;
        currStrip++;
        }
      
      // now copy the results over to the output
      for (pixel = 0; pixel < numPixels; pixel++)
        {
        outPtr2[pixel*numComps+comp] = ((OT *)tmpStrip)[pixel];
        }
      }
    outPtr2 += outIncr[2];
    self->UpdateProgress((idx2 - outExtent[4])/
                         (outExtent[5] - outExtent[4] + 1.0));
    } 

  delete [] tmpStrip;
}

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

  if (this->InternalFileName == NULL)
    {
    vtkErrorMacro("Either a FileName or FilePrefix must be specified.");
    return;
    }

  this->ComputeDataIncrements();
  
  // Call the correct templated function for the output
  void *outPtr;

  int res = this->Open(this->InternalFileName);
  if (!res || !this->Image)
    {
    this->Clean();
    return;
    }

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

  this->Clean();
}

//----------------------------------------------------------------------------
void vtkLSMReader::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
  os << indent << "OriginSpecifiedFlag: " << this->OriginSpecifiedFlag << endl;
  os << indent << "SpacingSpecifiedFlag: " << this->SpacingSpecifiedFlag << endl;
}
