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

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

#include "vtkByteSwap.h"
#include "vtkDirectory.h"
#include "vtkDoubleArray.h"
#include "vtkFloatArray.h"
#include "vtkImageData.h"
#include "vtkImageFlip.h"
#include "vtkImagePermute.h"
#include "vtkIntArray.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkShortArray.h"
#include "vtkUnsignedCharArray.h"

#include <ctype.h>

//----------------------------------------------------------------------------
vtkStandardNewMacro(vtkAnalyzeReader);
vtkCxxRevisionMacro(vtkAnalyzeReader, "$Revision: 1.16 $");

struct analyze_struct
{
  struct header_key
  {
    int sizeof_hdr;
    char data_type[10];
    char db_name[18];
    int extents;
    short int session_error;
    char regular;
    char hkey_un0;
  } hk;
  struct image_dimension                  /*      image_dimension  */
  {                                   /* off + size*/
    short int dim[8];               /* 0 + 16    */
    char vox_units[4];              /* 16 + 4    */
    char cal_units[8];              /* 20 + 4    */
    short int unused1;              /* 24 + 2    */
    short int datatype;             /* 30 + 2    */
    short int bitpix;               /* 32 + 2    */
    short int dim_un0;              /* 34 + 2    */
    float pixdim[8];                /* 36 + 32   */
    /*
      pixdim[] specifies the voxel dimensions:
      pixdim[1] - voxel width
      pixdim[2] - voxel height
      pixdim[3] - interslice distance
      ..etc
    */
    float vox_offset;                                   /* 68 + 4    */
    float funused1;                                     /* 72 + 4    */
    float funused2;                                     /* 76 + 4    */
    float funused3;                                     /* 80 + 4    */
    float cal_max;                                      /* 84 + 4    */
    float cal_min;                                      /* 88 + 4    */
    int compressed;                                     /* 92 + 4    */
    int verified;                                       /* 96 + 4    */
    int glmax, glmin;                                   /* 100 + 8   */
  } dime;
  struct data_history
  {
    char descrip[80];
    char aux_file[24];
    char orient;
    char originator[10];
    char generated[10];
    char scannum[10];
    char patient_id[10];
    char exp_date[10];
    char exp_time[10];
    char hist_un0[3];
    int views;
    int vols_added;
    int start_field;
    int field_skip;
    int omax, omin;
    int smax, smin;
  } dh;
};


// Constructor for a vtkAnalyzeReader.
vtkAnalyzeReader::vtkAnalyzeReader()
{
  this->FileDimensionality = 3;
  this->ReadSuccessful = 0;

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

vtkAnalyzeReader::~vtkAnalyzeReader()
{
};


void vtkAnalyzeReaderSwapLong(unsigned char *pntr)        
{
  unsigned char b0, b1, b2, b3;        
  b0 = *pntr;        
  b1 = *(pntr+1);
  b2 = *(pntr+2);        
  b3 = *(pntr+3);        
  *pntr = b3;
  *(pntr+1) = b2;        
  *(pntr+2) = b1;        
  *(pntr+3) = b0;        
}

void vtkAnalyzeReaderSwapShort(unsigned char *pntr)
{
  unsigned char b0, b1;        
  b0 = *pntr;        
  b1 = *(pntr+1);
  *pntr = b1;        
  *(pntr+1) = b0;        
}

void vtkAnalyzeReaderSwapHdr(struct analyze_struct *pntr)
{
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->hk.sizeof_hdr) ;
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->hk.extents) ;
  vtkAnalyzeReaderSwapShort((unsigned char *)&pntr->hk.session_error) ;
  vtkAnalyzeReaderSwapShort((unsigned char *)&pntr->dime.dim[0]) ;       
  vtkAnalyzeReaderSwapShort((unsigned char *)&pntr->dime.dim[1]) ;
  vtkAnalyzeReaderSwapShort((unsigned char *)&pntr->dime.dim[2]) ;       
  vtkAnalyzeReaderSwapShort((unsigned char *)&pntr->dime.dim[3]) ;
  vtkAnalyzeReaderSwapShort((unsigned char *)&pntr->dime.dim[4]) ;       
  vtkAnalyzeReaderSwapShort((unsigned char *)&pntr->dime.dim[5]) ;
  vtkAnalyzeReaderSwapShort((unsigned char *)&pntr->dime.dim[6]) ;       
  vtkAnalyzeReaderSwapShort((unsigned char *)&pntr->dime.dim[7]) ;
  vtkAnalyzeReaderSwapShort((unsigned char *)&pntr->dime.datatype) ;       
  vtkAnalyzeReaderSwapShort((unsigned char *)&pntr->dime.bitpix) ;
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->dime.pixdim[0]) ;
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->dime.pixdim[1]) ;
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->dime.pixdim[2]) ;
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->dime.pixdim[3]) ;
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->dime.pixdim[4]) ;
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->dime.pixdim[5]) ;
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->dime.pixdim[6]) ;
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->dime.pixdim[7]) ;
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->dime.vox_offset) ;
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->dime.compressed) ;
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->dime.verified) ;       
  vtkAnalyzeReaderSwapShort((unsigned char *)&pntr->dime.dim_un0) ;
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->dime.glmax) ;       
  vtkAnalyzeReaderSwapLong((unsigned char *)&pntr->dime.glmin) ;
}

int vtkAnalyzeReaderReadHeader(const char *fileName, 
                               struct analyze_struct *hdr,
                               int *swapped, int warn = 1)
{
  FILE *fp;
  
  if((fp=fopen(fileName,"rb"))==NULL)    
    {
    if(warn)
      {
      vtkGenericWarningMacro("Can't open: " << fileName);
      }
    
    return 0;
    }
  fread(hdr,1,sizeof(struct analyze_struct),fp);
  if(hdr->dime.dim[0] < 0 || hdr->dime.dim[0] > 15)
    {
    vtkAnalyzeReaderSwapHdr(hdr);
    *swapped = 1;
    }
  fclose(fp);
  return 1;
}

void vtkAnalyzeReaderConvertToRAS(struct analyze_struct *hdr, 
                                  vtkImageData *in,
                                  vtkImageData *output)
{
  int Row[3];
  int Column[3];
  int Slice[3];
  int RowCode = -1;
  int ColumnCode = -1;
  int i;
  
  // set up row colume and slice
  for (i = 0; i < 3; i++)
    {
    Row[i] = 0;
    Column[i] = 0;
    Slice[i] = 0;
    }
  
  switch (hdr->dh.orient)
    {
    case '0': // transverse
      RowCode = 1;
      Row[0] = -1;
      Column[1] = -1;
      Slice[2] = 1;
      break;
    case '1':
      RowCode = 1;
      Row[0] = -1;
      Column[2] = 1;
      Slice[1] = -1;
      break;      
    case '2':
      RowCode = 2;
      Row[1] = 1;
      Column[2] = 1;
      Slice[0] = 1;
      break;      
    case '3': // transverse
      RowCode = 1;
      Row[0] = -1;
      Column[1] = 1;
      Slice[2] = 1;
      break;
    case '4':
      RowCode = 1;
      Row[0] = -1;
      Column[2] = 1;
      Slice[1] = 1;
      break;      
    case '5':
      RowCode = 2;
      Row[1] = 1;
      Column[2] = 1;
      Slice[0] = -1;
      break;      
    default:
      RowCode = 1;
      Row[0] = -1;
      Column[1] = -1;
      Slice[2] = 1;
      break;
    }  

  vtkImageFlip *flip1 = vtkImageFlip::New();
  vtkImageFlip *flip2 = vtkImageFlip::New();
  vtkImageData *temps=0;
  vtkImagePermute *ip = vtkImagePermute::New();
  
  // first get the Rows to be x
  switch (RowCode)
    {
    case 0:
      temps = in;
      break;
    case 1:
      flip1->SetInput(in);
      flip1->SetFilteredAxis(0);
      flip2->SetInput(flip1->GetOutput());
      flip2->SetFilteredAxis(1);
      flip2->Update();
      temps = flip2->GetOutput();
      Column[0] = -Column[0];
      Column[1] = -Column[1];
      Column[2] = -Column[2];
      break;
    case 2:
      ip->SetInput(in);
      ip->SetFilteredAxes(1,0,2);
      flip1->SetInput(ip->GetOutput());
      flip1->SetFilteredAxis(0);
      flip1->Update();
      temps = flip1->GetOutput();
      Column[0] = Row[0];
      Column[1] = Row[1];
      Column[2] = Row[2];
      break;
    case 3:
      ip->SetInput(in);
      ip->SetFilteredAxes(1,0,2);
      flip1->SetInput(ip->GetOutput());
      flip1->SetFilteredAxis(1);
      flip1->Update();
      temps = flip1->GetOutput();
      Column[0] = -Row[0];
      Column[1] = -Row[1];
      Column[2] = -Row[2];
      break;
    case 4:
      ip->SetInput(in);
      ip->SetFilteredAxes(2,1,0);
      flip1->SetInput(ip->GetOutput());
      flip1->SetFilteredAxis(0);
      flip1->Update();
      temps = flip1->GetOutput();
      Column[0] = Slice[0];
      Column[1] = Slice[1];
      Column[2] = Slice[2];
      break;
    case 5:
      ip->SetInput(in);
      ip->SetFilteredAxes(2,1,0);
      flip1->SetInput(ip->GetOutput());
      flip1->SetFilteredAxis(2);
      flip1->Update();
      temps = flip1->GetOutput();
      Column[0] = -Slice[0];
      Column[1] = -Slice[1];
      Column[2] = -Slice[2];
      break;
    }
  
  for (i = 0; i < 3; i++)
    {
    if (Column[i] > 0)
      {
      Column[i] = 1;
      ColumnCode = i*2;
      }
    if (Column[i] < 0)
      {
      Column[i] = -1;
      ColumnCode = i*2+1;
      }
    }

  in = temps;
  // now get the columns to be y
  switch (ColumnCode)
    {
    case 2:
      temps = in;
      break;
    case 3:
      flip1->SetInput(in);
      flip1->SetFilteredAxis(1);
      flip2->SetInput(flip1->GetOutput());
      flip2->SetFilteredAxis(2);
      flip2->Update();
      temps = flip2->GetOutput();
      break;
    case 0:
    case 1:
      vtkGenericWarningMacro("Failure in coordinate conversion");
      break;
    case 4:
      ip->SetInput(in);
      ip->SetFilteredAxes(0,2,1);
      flip1->SetInput(ip->GetOutput());
      flip1->SetFilteredAxis(1);
      flip1->Update();
      temps = flip1->GetOutput();
      break;
    case 5:
      ip->SetInput(in);
      ip->SetFilteredAxes(0,2,1);
      flip1->SetInput(ip->GetOutput());
      flip1->SetFilteredAxis(2);
      flip1->Update();
      temps = flip1->GetOutput();
      break;
    }
  
  output->SetDimensions(temps->GetDimensions());
  output->SetSpacing(temps->GetSpacing());
  output->GetPointData()->SetScalars(
    temps->GetPointData()->GetScalars());
  
  flip1->Delete();
  flip2->Delete();
  ip->Delete();
}

// Reads an Analyze file and creates a vtkStructuredPoints dataset.
void vtkAnalyzeReader::ExecuteData(vtkDataObject*)
{
  vtkDataArray *newScalars = NULL;
  vtkImageData *output = this->GetOutput();
  vtkImageData *output1;
  struct analyze_struct hdr;
  
  // Initialize
  // Find the root of the file name
  char *fileRoot  = new char [strlen(this->FileName)+1];
  char *fileHdr  = new char [strlen(this->FileName)+10];
  char *fileImg  = new char [strlen(this->FileName)+10];
  int pos = (int)(strlen(this->FileName));
  sprintf(fileRoot,"%s",this->FileName);
  while (pos)
    {
    if (fileRoot[pos] == '.')
      {
      fileRoot[pos] ='\0';
      pos = 1;
      }
    pos--;
    }
  // construct the file names
  sprintf(fileHdr,"%s.hdr",fileRoot);
  sprintf(fileImg,"%s.img",fileRoot);
  delete [] fileRoot;
  
  // read in the header
  int swapped = 0;
  if (!vtkAnalyzeReaderReadHeader(fileHdr,&hdr,&swapped))
    {
    delete [] fileHdr;
    return;
    }
  delete [] fileHdr;
  
  // get some meta info
  char tmpString[82];

  strncpy(tmpString,hdr.dh.descrip,80);
  this->SetSeries(tmpString);
  strncpy(tmpString,hdr.dh.scannum,10);
  this->SetStudy(tmpString);
  strncpy(tmpString,hdr.dh.patient_id,10);
  this->SetPatientID(tmpString);
  strncpy(tmpString,hdr.dh.exp_date,10);
  this->SetDate(tmpString);
  
  
  // set parameters
  strncpy(tmpString,hdr.dime.vox_units,4);
  //this->SetUnits(tmpString);

  int dims[3];
  dims[0] = (hdr.dime.dim[1] > 0) ? hdr.dime.dim[1] : 1;
  dims[1] = (hdr.dime.dim[2] > 0) ? hdr.dime.dim[2] : 1;
  dims[2] = (hdr.dime.dim[3] > 0) ? hdr.dime.dim[3] : 1;
    
  int totalVox = dims[0]*dims[1]*dims[2];
  int dataSize = 1;
  
  // what type of data
  switch (hdr.dime.datatype)
    {
    case 2:
      newScalars = vtkUnsignedCharArray::New();
      newScalars->SetNumberOfComponents(1);
      break;
    case 4:
      newScalars = vtkShortArray::New();
      newScalars->SetNumberOfComponents(1);
      dataSize = 2;
      break;
    case 8:
      newScalars = vtkIntArray::New();
      newScalars->SetNumberOfComponents(1);
      dataSize = 4;
      break;
    case 16:
      newScalars = vtkFloatArray::New();
      newScalars->SetNumberOfComponents(1);
      dataSize = 4;
      break;
    case 64:
      newScalars = vtkDoubleArray::New();
      newScalars->SetNumberOfComponents(1);
      dataSize = 8;
      break;
    case 128:
      newScalars = vtkUnsignedCharArray::New();
      output->SetNumberOfScalarComponents(3);
      newScalars->SetNumberOfComponents(3);
      break;
    default:
      vtkWarningMacro("Unknown Anaylze pixel format!!!");
    }
    
  // OK, lets finally go get the data
  newScalars->SetNumberOfTuples(totalVox);
  unsigned char *data = (unsigned char *)newScalars->GetVoidPointer(0);
  
  FILE *fp = fopen(fileImg,"rb");
  if (!fp)
    {
    vtkErrorMacro("Unable to open Analyze .img file: " << fileImg);
    delete [] fileImg;
    return;
    }
  delete [] fileImg;
  
  // read left handed coords into right handed coords
  fread(data,1,totalVox*dataSize,fp);
  fclose(fp);
    
  // do we need to swap ?
  if(swapped)
    {
    if (dataSize == 2)
      {
      vtkByteSwap::SwapVoidRange(data, totalVox, 2);
      }
    if (dataSize == 4)
      {
      vtkByteSwap::SwapVoidRange(data, totalVox, 4);
      }
    }
  
  output1 = vtkImageData::New();
  output1->SetDimensions(dims[0],dims[1],dims[2]);

  if( !SpacingSpecifiedFlag )
    {
    output1->SetSpacing(hdr.dime.pixdim[1],
                      hdr.dime.pixdim[2],
                      hdr.dime.pixdim[3]);
    }
  
  if( newScalars )
    {
    output1->GetPointData()->SetScalars(newScalars);
    newScalars->Delete();
    }    
  output1->SetWholeExtent(output1->GetExtent());
  output1->SetScalarType(newScalars->GetDataType());
  output1->SetNumberOfScalarComponents(newScalars->GetNumberOfComponents());

  vtkAnalyzeReaderConvertToRAS(&hdr,output1,output);
  
  this->ReadSuccessful = 1;
  output1->Delete();
}

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

  os << indent << "File Name: " 
     << (this->FileName ? this->FileName : "(none)") << "\n";
  os << indent << "ReadSuccessful: " << this->ReadSuccessful << endl;
  os << indent << "OriginSpecifiedFlag: " << this->OriginSpecifiedFlag << endl;
  os << indent << "SpacingSpecifiedFlag: " << this->SpacingSpecifiedFlag << endl;
}

  // Not used now, but will be needed when this is made into an 
  // imaging filter.
  // Sets WholeExtent, Origin, Spacing, ScalarType 
  // and NumberOfComponents of output.
void vtkAnalyzeReader::ExecuteInformation()
{
  vtkImageData *output = this->GetOutput();

  struct analyze_struct hdr;
  
  // Initialize
  // Find the root of the file name
  char *fileRoot  = new char [strlen(this->FileName)+10];
  char *fileHdr  = new char [strlen(this->FileName)+10];
  int pos = (int)(strlen(this->FileName));
  sprintf(fileRoot,"%s",this->FileName);
  while (pos)
    {
    if (fileRoot[pos] == '.')
      {
      fileRoot[pos] ='\0';
      pos = 1;
      }
    pos--;
    }
  // construct the file names
  sprintf(fileHdr,"%s.hdr",fileRoot);
  delete [] fileRoot;
  
  // read in the header
  int swapped = 0;
  if (!vtkAnalyzeReaderReadHeader(fileHdr,&hdr,&swapped))
    {
    delete [] fileHdr;
    return;
    }
  delete [] fileHdr;
  
  // get some meta info
  char tmpString[82];

  strncpy(tmpString,hdr.dh.descrip,80);
  this->SetSeries(tmpString);
  strncpy(tmpString,hdr.dh.scannum,10);
  this->SetStudy(tmpString);
  strncpy(tmpString,hdr.dh.patient_id,10);
  this->SetPatientID(tmpString);
  strncpy(tmpString,hdr.dh.exp_date,10);
  this->SetDate(tmpString);
  
  
  // set parameters
  strncpy(tmpString,hdr.dime.vox_units,4);
  //this->SetUnits(tmpString);

  int dims[3];
  dims[0] = (hdr.dime.dim[1] > 0) ? hdr.dime.dim[1] : 1;
  dims[1] = (hdr.dime.dim[2] > 0) ? hdr.dime.dim[2] : 1;
  dims[2] = (hdr.dime.dim[3] > 0) ? hdr.dime.dim[3] : 1;
    
  // set the data type
  switch (hdr.dime.datatype)
    {
    case 2:
      output->SetScalarType(VTK_UNSIGNED_CHAR);
      output->SetNumberOfScalarComponents(1);
      break;
    case 4:
      output->SetScalarType(VTK_SHORT);
      output->SetNumberOfScalarComponents(1);
      break;
    case 8:
      output->SetScalarType(VTK_INT);
      output->SetNumberOfScalarComponents(1);
      break;
    case 16:
      output->SetScalarType(VTK_FLOAT);
      output->SetNumberOfScalarComponents(1);
      break;
    case 64:
      output->SetScalarType(VTK_DOUBLE);
      output->SetNumberOfScalarComponents(1);
      break;
    case 128:
      output->SetScalarType(VTK_UNSIGNED_CHAR);
      output->SetNumberOfScalarComponents(3);
      break;
    default:
      vtkWarningMacro("Unknown Anaylze pixel format!!!");
    }
    
  output->SetDimensions(dims[0],dims[1],dims[2]);

  if( !SpacingSpecifiedFlag )
    {
    output->SetSpacing(hdr.dime.pixdim[1],
                     hdr.dime.pixdim[2],
                     hdr.dime.pixdim[3]);
    }
  
  output->SetWholeExtent(output->GetExtent());

  this->SetDataExtent( output->GetExtent() );

  if( !SpacingSpecifiedFlag )
    {
    this->SetDataSpacing( hdr.dime.pixdim[1],
                        hdr.dime.pixdim[2],
                        hdr.dime.pixdim[3]);
    }
  this->SetNumberOfScalarComponents(output->GetNumberOfScalarComponents());
  this->SetDataScalarType(output->GetScalarType());
}


int vtkAnalyzeReader::CanReadFile(const char* fname)
{
  struct analyze_struct hdr;
 
  // We check if the filename has extension .img or .hdr
  std::string fnameString( fname );
  std::string::size_type posExtension = fnameString.length() - 4;
  
  std::string::size_type posImg = fnameString.rfind(".img");
  std::string::size_type posHdr = fnameString.rfind(".hdr");

  if ( posImg == std::string::npos && posHdr == std::string::npos )
    {
    return 0;
    }

  if ( posImg != posExtension && posHdr != posExtension )
    {
    return 0;
    }
  
  // Initialize
  // Find the root of the file name
  char *fileRoot  = new char [strlen(fname)+1];
  char *fileHdr  = new char [strlen(fname)+10];
  char *fileImg  = new char [strlen(fname)+10];
  int pos = (int)(strlen(fname));
  sprintf(fileRoot,"%s",fname);
  while (pos)
    {
    if (fileRoot[pos] == '.')
      {
      fileRoot[pos] ='\0';
      pos = 1;
      }
    pos--;
    }
  // construct the file names
  sprintf(fileHdr,"%s.hdr",fileRoot);
  sprintf(fileImg,"%s.img",fileRoot);
  delete [] fileRoot;

  // read in the header
  int swapped = 0;
  if (!vtkAnalyzeReaderReadHeader(fileHdr,&hdr,&swapped, 0))
    {
    delete [] fileHdr;
    delete [] fileImg;
    return 0;
    }
  delete [] fileHdr;
  FILE *fp = fopen(fileImg,"rb");
  delete [] fileImg;
  if (!fp)
    {
    return 0;
    }
  fclose(fp); 
  switch (hdr.dime.datatype)
    {
    case 2:
      break;
    case 4:
      break;
    case 8:
      break;
    case 16:
      break;
    case 64:
      break;
    case 128:
      break;
    default:
      return 0;
    }
  // if we make it this far it means that
  // there was a filename.hdr and filename.img and the
  // filename.hdr had a header that had a datatype of 2,4, 8, 16, 64 or 128
  // so it must be an analyze format....
  return 3;
}




