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

  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 "vtkGESignaReader3D.h"

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

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

vtkCxxRevisionMacro(vtkGESignaReader3D, "$Revision: 1.11 $");
vtkStandardNewMacro(vtkGESignaReader3D);

#define FindAxis( A, B )                                        \
((A[0] != B[0] && A[1] == B[1] && A[2] == B[2])?(0):            \
  ((A[0] == B[0] && A[1] != B[1] && A[2] == B[2])?(1):          \
   ((A[0] == B[0] && A[1] == B[1] && A[2] != B[2])?(2):(3))))
 
vtkGESignaReader3D::vtkGESignaReader3D()
{
  this->FileDimensionality = 3;
  this->ImageFileName = NULL;
  this->BaseImageFileName = NULL;
  this->ImageExtent[0] = 0;
  this->ImageExtent[1] = 0;
  this->ImageNumberOfDigits = 0;
}

// From the file name find the first/last image in the series
// in that directory
void vtkGESignaReader3D::ComputeImageExtent()
{
  if ( !this->FileName )
    {
    vtkErrorMacro("A FileName is required");
    return;
    }
  
  if ( this->BaseImageFileName )
    {
    delete [] this->BaseImageFileName;
    }
  
  if ( this->ImageFileName )
    {
    delete [] this->ImageFileName;
    }
  
  this->ImageFileName = new char [strlen(this->FileName)+1];
  this->BaseImageFileName = new char [strlen(this->FileName)+1];
  sprintf( this->BaseImageFileName, "%s", this->FileName );
  
  int i;
  i = (int)(strlen(this->FileName)-1);
  while ( i )
    {
    if ( this->BaseImageFileName[i] == '/' )
      {
      vtkErrorMacro("Invalid FileName structure");
      return;
      }
    
    if ( this->BaseImageFileName[i] == 'i' || this->BaseImageFileName[i] == 'I' )
      {
      break;
      }
    else
      {
      this->BaseImageFileName[i] = 0;
      }
    
    i--;
    }
  
  if ( i == 0 )
    {
    vtkErrorMacro("Invalid FileName structure");
    return;
    }
  
  int imageNum = 0;
  int tmp;
  int count = 0;
  for ( count = 0;
        count+i+1 < static_cast<int>(strlen(this->FileName)); 
        count++ )
    {
    tmp = this->FileName[i+1+count] - '0';
    if ( tmp < 0 || tmp > 9 )
      {
      break;
      }
    imageNum = imageNum*10 + tmp;
    }
  
  this->ImageNumberOfDigits = count;
  
  if ( count > 9 )
    {
    vtkErrorMacro("Too many digits in image number");
    return;
    }
  
  this->ImageFormatString[0] = '%';
  this->ImageFormatString[1] = 's';
  this->ImageFormatString[2] = '%';
  this->ImageFormatString[3] = '0';
  this->ImageFormatString[4] = this->ImageNumberOfDigits + '0';
  this->ImageFormatString[5] = 'd';
  this->ImageFormatString[6] = '.';
  this->ImageFormatString[7] =  this->FileName[strlen(this->FileName)-2];
  this->ImageFormatString[8] =  this->FileName[strlen(this->FileName)-1];
  this->ImageFormatString[9] =  0;

  this->ImageExtent[0] = -1;
  this->ImageExtent[1] = -1;
  
  struct stat fs;
  // Find the first consecutive file in the image series
  for ( i = imageNum; i >= 0; i-- )
    {
    this->ComputeImageFileName( i );
    if ( (stat(this->ImageFileName, &fs) != 0) )
      {
      break;
      }
    this->ImageExtent[0] = i;
    }

  // Find the last consecutive file in the image series
  for ( i = imageNum; 1; i++ )
    {
    this->ComputeImageFileName( i );
    if ( (stat(this->ImageFileName, &fs) != 0) )
      {
      break;
      }
    this->ImageExtent[1] = i;
    }

}

// For a given slice, what is the file name associate with
// that image
void vtkGESignaReader3D::ComputeImageFileName( int slice )
{
  sprintf(this->ImageFileName, this->ImageFormatString, this->BaseImageFileName, slice);
}

int vtkGESignaReader3D::ReadHeader( char *filename, 
                                    float TLHC[3],
                                    float TRHC[3],
                                    float BRHC[3],
                                    float spacing[3],
                                    int   size[2] )
{
  FILE *fp = fopen(filename, "rb");
  if (!fp)
    {
    vtkErrorMacro("Unable to open file " << filename);
    return 0;
    }

  int magic;
  fread(&magic, 4, 1, fp);
  vtkByteSwap::Swap4BE(&magic);
  
  if (magic != 0x494d4746)
    {
    vtkErrorMacro(<<"Unknown file type! Not a GE ximg file!");
    fclose(fp);
    return 0;
    }
  
  // read in the pixel offset from the header
  int offset;
  fread(&offset, 4, 1, fp);
  vtkByteSwap::Swap4BE(&offset);
  this->SetHeaderSize(offset);

  int width, height, depth;
  fread(&width, 4, 1, fp);
  vtkByteSwap::Swap4BE(&width);
  fread(&height, 4, 1, fp);
  vtkByteSwap::Swap4BE(&height);
  // depth in bits
  fread(&depth, 4, 1, fp);
  vtkByteSwap::Swap4BE(&depth);

  size[0] = width;
  size[1] = height;
  
  int compression;
  fread(&compression, 4, 1, fp);
  vtkByteSwap::Swap4BE(&compression);

  // seek to the exam series and image header offsets
  fseek(fp, 132, SEEK_SET);
  int examHdrOffset;
  fread(&examHdrOffset, 4, 1, fp);
  vtkByteSwap::Swap4BE(&examHdrOffset);
  fseek(fp, 140, SEEK_SET);
  int seriesHdrOffset;
  fread(&seriesHdrOffset, 4, 1, fp);
  vtkByteSwap::Swap4BE(&seriesHdrOffset);
  fseek(fp, 148, SEEK_SET);
  int imgHdrOffset;
  fread(&imgHdrOffset, 4, 1, fp);
  vtkByteSwap::Swap4BE(&imgHdrOffset);

  // seek to the exam and read some info
  fseek(fp, examHdrOffset + 84, SEEK_SET);
  char tmpStr[1024];
  fread(tmpStr,13,1,fp);
  tmpStr[13] = 0;
  this->SetPatientID(tmpStr);
  fread(tmpStr,25,1,fp);
  tmpStr[25] = 0;
  this->SetPatientName(tmpStr);
  
  // seek to the series and read some info
  fseek(fp, seriesHdrOffset + 10, SEEK_SET);
  short series;
  fread(&series,2,1,fp);
  vtkByteSwap::Swap2BE(&series);
  sprintf(tmpStr,"%d",series);
  this->SetSeries(tmpStr);
  fseek(fp, seriesHdrOffset + 92, SEEK_SET);
  fread(tmpStr,25,1,fp);
  tmpStr[25] = 0;
  this->SetStudy(tmpStr);

  // now seek to the image header and read some values
  float tmpZ;
  float spacingX, spacingY, spacingZ;
  fseek(fp, imgHdrOffset + 50, SEEK_SET);
  fread(&spacingX, 4, 1, fp);
  vtkByteSwap::Swap4BE(&spacingX);
  fread(&spacingY, 4, 1, fp);
  vtkByteSwap::Swap4BE(&spacingY);
  fseek(fp, imgHdrOffset + 116, SEEK_SET);  
  fread(&spacingZ, 4, 1, fp);
  vtkByteSwap::Swap4BE(&spacingZ);
  fseek(fp, imgHdrOffset + 26, SEEK_SET);  
  fread(&tmpZ, 4, 1, fp);
  vtkByteSwap::Swap4BE(&tmpZ);
  spacingZ = spacingZ + tmpZ;

  spacing[0] = spacingX;
  spacing[1] = spacingY;
  spacing[2] = spacingZ;
  
  fseek(fp, imgHdrOffset + 154, SEEK_SET);

  // read TLHC
  fread(&(TLHC[0]), 4, 1, fp);
  vtkByteSwap::Swap4BE(&(TLHC[0]));
  fread(&(TLHC[1]), 4, 1, fp);
  vtkByteSwap::Swap4BE(&(TLHC[1]));
  fread(&(TLHC[2]), 4, 1, fp);
  vtkByteSwap::Swap4BE(&(TLHC[2]));

  // read TRHC
  fread(&(TRHC[0]), 4, 1, fp);
  vtkByteSwap::Swap4BE(&(TRHC[0]));
  fread(&(TRHC[1]), 4, 1, fp);
  vtkByteSwap::Swap4BE(&(TRHC[1]));
  fread(&(TRHC[2]), 4, 1, fp);
  vtkByteSwap::Swap4BE(&(TRHC[2]));

  // read BRHC
  fread(&(BRHC[0]), 4, 1, fp);
  vtkByteSwap::Swap4BE(&(BRHC[0]));
  fread(&(BRHC[1]), 4, 1, fp);
  vtkByteSwap::Swap4BE(&(BRHC[1]));
  fread(&(BRHC[2]), 4, 1, fp);
  vtkByteSwap::Swap4BE(&(BRHC[2]));
  
  // close the file
  fclose(fp);
  
  return 1;
}

//Assume that the FileName field has been set to one of the
// files in a series, the name is e*s*i*.MR or .CT. We will read
// all consecutive images before or after the one given
// by the filename (in the same series).

void vtkGESignaReader3D::ExecuteInformation()
{
  this->ComputeImageExtent();
  
  if ( this->ImageExtent[1] - this->ImageExtent[0] < 1 )
    {
    vtkErrorMacro("vtkGESignaReader3D can only be used to read a volume." << endl <<
                  "This file appears to have just 1 image - use vtkGESignaReader instead.");
    return;
    }
  
  this->ComputeImageFileName(this->ImageExtent[0]);
  if (this->ImageFileName == NULL)
    {
    return;
    }

  float TLHC[3], TRHC[3], BRHC[3];
  float spacing[3];
  int size[2];

  if ( !this->ReadHeader( this->ImageFileName, TLHC, TRHC, BRHC, spacing, size ) )
    {
    return;
    }
  
  float TLHC2[3], TRHC2[3], BRHC2[3];
  float spacing2[3];
  int size2[2];
  this->ComputeImageFileName(this->ImageExtent[0]+1);
  if (this->ImageFileName == NULL)
    {
    return;
    }
  if ( !this->ReadHeader( this->ImageFileName, TLHC2, TRHC2, BRHC2, spacing2, size2 ) )
    {
    return;
    } 
  
  int imageAxisType[3];
  
  imageAxisType[0] = FindAxis( TLHC, TRHC );
  imageAxisType[1] = FindAxis( TRHC, BRHC );
  imageAxisType[2] = FindAxis( TLHC, TLHC2 );
  
  if ( imageAxisType[0] == 3 ||
       imageAxisType[1] == 3 ||
       imageAxisType[2] == 3 )
    {
    vtkErrorMacro("Invalid axes found in data");
    return;
    }

  
  float Axis[3][3];
  
  Axis[0][0] = TRHC[0] - TLHC[0];
  Axis[0][1] = TRHC[1] - TLHC[1];
  Axis[0][2] = TRHC[2] - TLHC[2];
  
  Axis[1][0] = TRHC[0] - BRHC[0];
  Axis[1][1] = TRHC[1] - BRHC[1];
  Axis[1][2] = TRHC[2] - BRHC[2];

  Axis[2][0] = TLHC2[0] - TLHC[0];
  Axis[2][1] = TLHC2[1] - TLHC[1];
  Axis[2][2] = TLHC2[2] - TLHC[2];

  int raiseTwo[3];
  raiseTwo[0] = 1;
  raiseTwo[1] = 2;
  raiseTwo[2] = 4;
  
  int dim[3];
  dim[0] = size[0];
  dim[1] = size[1];
  dim[2] = this->ImageExtent[1] - this->ImageExtent[0] + 1;
  
  double origin[3];
  origin[0] = TLHC[0] + BRHC[0] - TRHC[0];
  origin[1] = TLHC[1] + BRHC[1] - TRHC[1];
  origin[2] = TLHC[2] + BRHC[2] - TRHC[2];
  
  int originType = 0;
  int i;
  for ( i = 0; i < 3; i++ )
    {
    if ( Axis[i][imageAxisType[i]] < 0 )
      {
      originType += raiseTwo[imageAxisType[i]];
      origin[imageAxisType[i]] -= 
        (dim[imageAxisType[i]]-1)*spacing[imageAxisType[i]];
      }
    }
  

  this->SetDataOrigin(origin);
  
  this->DataExtent[0] = 0;
  this->DataExtent[2] = 0;
  this->DataExtent[4] = 0;
  
  double permutedSpacing[3];
  for ( i = 0; i < 3; i++ )
    {
    this->DataExtent[imageAxisType[i]*2+1] = dim[i] - 1;
    permutedSpacing[imageAxisType[i]] = spacing[i];
    }
  
  // Where is the origin of the input data in the vtkImageData?
  this->OriginOffset = 0;
  if ( originType%2 )
    {
    this->OriginOffset += this->DataExtent[1];
    }
  if ( (originType/2)%2 )
    {
    this->OriginOffset += 
      (this->DataExtent[3] * (this->DataExtent[1]+1));
    }
  if ( (originType/4)%2 )
    {
    this->OriginOffset += this->DataExtent[5] *
      ((this->DataExtent[3]+1) * (this->DataExtent[1]+1));
    }
  
  
  // One step in x, y, z in the input data
  // means what in the vtkImageData?
  for ( i = 0; i < 3; i++ )
    {
    switch ( imageAxisType[i] )
      {
      case 0:
        this->ImageIncrement[i] = 1;
        break;
      case 1:
        this->ImageIncrement[i] = this->DataExtent[1] + 1;
        break;
      case 2:
        this->ImageIncrement[i] = 
          (this->DataExtent[1] + 1) *
          (this->DataExtent[3] + 1);
        break;
      }
    if ( Axis[i][imageAxisType[i]] < 0 )
      {
      this->ImageIncrement[i] = -this->ImageIncrement[i];
      }
    }
  
  this->SetDataScalarTypeToUnsignedShort();

  this->SetNumberOfScalarComponents(1);
  this->SetDataSpacing(permutedSpacing);
  this->vtkImageReader2::ExecuteInformation();
}


// A copy from the superclass - maybe it can be made into a member function
// then it won't need to be copied?
static void vtkcopygenesisimage(FILE *infp, int width, int height, 
                                int compress,
                                short *map_left, short *map_wide,
                                unsigned short *output)
{
  unsigned short row;
  unsigned short last_pixel=0;
  for (row=0; row<height; ++row) 
    {
      unsigned short j;
      unsigned short start;
      unsigned short end;
      
      if (compress == 2 || compress == 4) 
        { // packed/compacked
          start=map_left[row];
          end=start+map_wide[row];
        }
      else 
        {
          start=0;
          end=width;
        }
      // Pad the first "empty" part of the line ...
      for (j=0; j<start; j++) 
        {
          (*output) = 0;
          ++output;
        }

      if (compress == 3 || compress == 4) 
        { // compressed/compacked
          while (start<end) 
            {
              unsigned char byte;
              if (!fread(&byte,1,1,infp))
                {
                  return;
                }
        if ( byte != 0 )
        {byte = byte;}

              if (byte & 0x80) 
                {
                  unsigned char byte2;
                  if (!fread(&byte2,1,1,infp))
                    {
                      return;
                    }
                  if (byte & 0x40) 
                    {      // next word
                      if (!fread(&byte,1,1,infp))
                        {
                          return;
                        }
                      last_pixel=
                        (((unsigned short)byte2<<8)+byte);
                    }
                  else 
                    {                  // 14 bit delta
                      if (byte & 0x20) 
                        {
                          byte|=0xe0;
                        }
                      else 
                        {
                          byte&=0x1f;
                        }
                      last_pixel+=
                        (((short)byte<<8)+byte2);
                    }
                }
              else 
                {                          // 7 bit delta
                  if (byte & 0x40) 
                    {
                      byte|=0xc0;
                    }
                  last_pixel+=(signed char)byte;
                }
              (*output) = last_pixel;
              ++output;
              ++start;
            }
        }
      else 
        {
          while (start<end) 
            {
              unsigned short u;
              if (!fread(&u,2,1,infp))
                {
                  return;
                }
              vtkByteSwap::Swap2BE(&u);
              (*output) = u;
              ++output;
              ++start;
            }
        }
      
      // Pad the last "empty" part of the line ...
      for (j=end; j<width; j++) 
        {
          (*output) = 0;
          ++output;
        }
    }
}

static void vtkGESignaReader3DUpdate2(vtkGESignaReader3D *self, 
                                      unsigned short* outPtr, 
              int* vtkNotUsed(outExt), int zLoc)
{
  FILE *fp = fopen(self->GetImageFileName(), "rb");
  if (!fp)
    {
    return;
    }

  int magic;
  fread(&magic, 4, 1, fp);
  vtkByteSwap::Swap4BE(&magic);
  
  if (magic != 0x494d4746)
    {
    vtkGenericWarningMacro(<<"Unknown file type! Not a GE ximg file!");
    fclose(fp);
    return;
    }

  // read in the pixel offset from the header
  int offset;
  fread(&offset, 4, 1, fp);
  vtkByteSwap::Swap4BE(&offset);

  int width, height, depth;
  fread(&width, 4, 1, fp);
  vtkByteSwap::Swap4BE(&width);
  fread(&height, 4, 1, fp);
  vtkByteSwap::Swap4BE(&height);
  // depth in bits
  fread(&depth, 4, 1, fp);
  vtkByteSwap::Swap4BE(&depth);

  int compression;
  fread(&compression, 4, 1, fp);
  vtkByteSwap::Swap4BE(&compression);

  short *leftMap = 0;
  short *widthMap = 0;

  if (compression == 2 || compression == 4) 
    { // packed/compacked
      leftMap = new short [height];
      widthMap = new short [height];

      fseek(fp, 64, SEEK_SET);
      int packHdrOffset;
      fread(&packHdrOffset, 4, 1, fp);
      vtkByteSwap::Swap4BE(&packHdrOffset);
      
      // now seek to the pack header and read some values
      fseek(fp, packHdrOffset, SEEK_SET);
      // read in the maps
      int i;
      for (i = 0; i < height; i++)
        {
          fread(leftMap+i, 2, 1, fp);
          vtkByteSwap::Swap2BE(leftMap+i);
          fread(widthMap+i, 2, 1, fp);
          vtkByteSwap::Swap2BE(widthMap+i);
        }
    }

  // seek to pixel data
  fseek(fp, offset, SEEK_SET);

  // read in the pixels
  unsigned short *tmp = new unsigned short [width*height];
  vtkcopygenesisimage(fp, width, height, 
                      compression, leftMap, widthMap, tmp);

  // now copy into desired extent
  int yp;
  unsigned short *sliceStart, *rowStart;
  sliceStart = outPtr + self->OriginOffset + zLoc*self->ImageIncrement[2];
  
  int xLoc;
  for (yp = 0; yp < height; ++yp)
    {
    int ymod = height - yp - 1;
    rowStart = sliceStart + ymod * self->ImageIncrement[1];
    for ( xLoc = 0; xLoc < width; xLoc++ )
      {
      *(rowStart + xLoc*self->ImageIncrement[0]) =
        *(tmp + yp*width + + xLoc);
      if ( *(rowStart + xLoc*self->ImageIncrement[0]) > 3000 )
        {
        xLoc = xLoc;
        }
      }
    }

  delete [] tmp;
  if (leftMap)
    {
      delete [] leftMap;
    }
  if (widthMap)
    {
      delete [] widthMap;
    }
  fclose(fp);
}

static void vtkGESignaReader3DUpdate(vtkGESignaReader3D *self, vtkImageData *data, 
                                   unsigned short *outPtr)
{
  int outExtent[6];

  unsigned short *outPtr2;

  data->GetExtent(outExtent);

  outPtr2 = outPtr;
  int idx2;
  for (idx2 = self->ImageExtent[0]; idx2 <= self->ImageExtent[1]; ++idx2)
    {
    self->ComputeImageFileName(idx2);
    // read in a PNG file
    vtkGESignaReader3DUpdate2(self, outPtr2, outExtent, idx2-self->ImageExtent[0]);
    self->UpdateProgress((idx2 - self->ImageExtent[0])/
                         (self->ImageExtent[1] - self->ImageExtent[0] + 1.0));
    }
}


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

  if (this->FileName == NULL)
    {
    vtkErrorMacro(<< "A FileName must be specified.");
    return;
    }

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

  // Call the correct templated function for the input
  outPtr = data->GetScalarPointer();
  vtkGESignaReader3DUpdate(this, data, (unsigned short *)(outPtr));
}

void vtkGESignaReader3D::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  
  os << indent << "ImageFileName: ";
  if (this->ImageFileName)
    {
    os << this->ImageFileName << endl;
    }
  else
    {
    os << "(none)" << endl;
    }
}

