/**
 * @file  FieldDRTG.h
 * @brief Container class to hold a 3D vector image, like velocity or displacement.
 *
 * Copyright (c) 2008, 2009, 2012, 2013 University of Pennsylvania.
 *
 * This file is part of DTI-DROID.
 *
 * DTI-DROID is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * DTI-DROID is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with DTI-DROID.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Contact: SBIA Group <sbia-software at uphs.upenn.edu>
 */

#ifndef _DTIDROID_FIELDDRTG_H
#define _DTIDROID_FIELDDRTG_H


/*** added by lbloy 11/28/08*/
#include <cstring>
#include <stdlib.h>
/** End Edit */


#include <string>

/**
*  @brief	Container class to hold a 3D vector image,  like velocity, displacement 
*		or a tensor. This is the parent class for all derived field types.
* 		
*		The class is templated on the datatype of the vector component, and the 
*		number of components. The class	assumes that double and float will be the 
*		common types.
*  @author	Hari Sundar
*  @date	1/10/06, 8/6/06
* 
* Container class to hold a 3D vector image,  like velocity, displacement or a tensor. 
* This is the parent class for all derived field types.		
*
* The class is templated on the datatype of the vector component, and the  number of 
* components. The class	assumes that double and float will be the common type. The data 
* is stored in a single C-array of dimension n*x*y*z.
* 
**/


template<typename T, int n>
class Field {
  public:

    /** @name Constructors and destructors **/
    //@{
    Field();
    Field(const Field<T, n>& rhs);
    virtual ~Field();
    //@}

    /**
     *  @brief		initizlizes the Field, from disk 
     *  @param		fileName string containing the Field header file in the MetaIO
     * 				format.
     *  @return		true if successfull, false otherwise
    **/
    virtual bool init(std::string fileName);

    /**
     *  @brief		initizlizes the Field 
     *  @param		sizeX the dimension of the field along the X axis
     *  @param		sizeY the dimension of the field along the Y axis
     *  @param		sizeZ the dimension of the field along the Z axis 
     *  @return		true if successfull, false otherwise
     *
     *  Follow by reading in the raw field file if required.
    **/
    bool init(int sizeX, int sizeY, int sizeZ);

    /**
    * @brief                        clean the field data. reset the parameters.
    * @note                         added by Jinzhong Yang, Aug. 3, 2007
    **/
    void clear();
    
    /**
    *@brief                        copy the field data from the other one.
    *@param                      rhs     the field data to be copied from.
    *@return                      none.
    *@note                         added by Jinzhong yang, Feb. 26, 2008
    **/
    void copy(const Field<T, n>& rhs);
    
    /**
     *  @brief		return the size of the Field in the specified direction.
     *  @param		dim	Dimension, must be 0, 1, or 2.
     *  @return		dimension size.
     *
    **/   
    int getSize( int dim ) const { return m_Dims[dim]; };

    /**
     *  @brief		return the size of a voxel in the specified direction.
     *  @param		dim	Dimension, must be 0, 1, or 2.
     *  @return		voxel size in specified direction.
     *
    **/
    double getVoxelSize( int dim ) const { return m_VoxelSize[dim]; };

    /**
     *  @brief		set the size of a voxel in the specified direction.
     *  @param		dim	Dimension, must be 0, 1, or 2.
     *  @param		voxelSize	the desired voxel size.	
    **/
    void setVoxelSize( int dim, double voxelSize ) { m_VoxelSize[dim] = voxelSize; };

    /**
     *  @brief		returns the file name of the raw volume.
     *  @return		CString for the filename.
    **/
    const std::string& getRawFileName() const { return m_RawFileName; };

    /**
     *  @brief		return pointer to data.
     *  @return		pointer to the field data.
     *
    **/
    T* getDataPtr() const { return m_Data; };

    /**
     *  @brief		return pointer to data at voxel location.
     *  @param		x,y,z the voxel location.
     *  @return		pointer to the field data.
     *
    **/
    T* getAt(int x, int y, int z) const { return m_Data + n*(m_Dims[0]*(z*m_Dims[1] + y) +x) ; }

    /**
     *  @brief		Exports the raw field into a file.
     *  @param		fileName The filename as a STL string.
     *  @return		true if successful, false otherwise.
    **/
    bool exportRawFieldToFile(std::string fileName);

    /**
     *  @brief		Imports a raw field from a file.
     *  @param		fileName The filename as a STL string.
     *  @return		true if successful, false otherwise.
    **/
    bool importRawFieldFromFile(std::string fileName);


    bool exportRawFieldToFile_BE(std::string fileName);


    bool importRawFieldFromFile_BE(std::string fileName);


    int load_data_BE(const char *fname, void *buf, int size);

    int save_data_BE(const char *fname, void *buf, int size);

    // member variable definitions
  protected:
    /// Filename of Field data (with full path)
    std::string m_RawFileName;
    /// x,y,z dimensions of field
    int m_Dims[3];
    /// Pointer to the field data
    T* m_Data;
    /// Voxel size
    double m_VoxelSize[3];

};

template <typename T, int n>
Field<T, n>::Field() {
  m_RawFileName = std::string("none");
  m_Dims[0] = 0; m_Dims[1] = 0; m_Dims[2] = 0;
  m_VoxelSize[0] = 1.0; m_VoxelSize[1] = 1.0; m_VoxelSize[2] = 1.0;
  m_Data = NULL;
};

template <typename T, int n>
Field<T, n>::Field(const Field<T, n>& rhs) {
  m_RawFileName = rhs.m_RawFileName;
  m_Dims[0] = rhs.m_Dims[0]; m_Dims[1] = rhs.m_Dims[1]; m_Dims[2] = rhs.m_Dims[2];
  m_VoxelSize[0] = rhs.m_VoxelSize[0];m_VoxelSize[1] = rhs.m_VoxelSize[1];m_VoxelSize[2] = rhs.m_VoxelSize[2];
  m_Data = new T[n*m_Dims[0]*m_Dims[1]*m_Dims[2]];
  memcpy(m_Data, rhs.getDataPtr(), n*m_Dims[0]*m_Dims[1]*m_Dims[2]*sizeof(T));
}

template <typename T, int n>
Field<T,n>::~Field() {
  // delete the memory ...
  if (m_Data)
    delete [] m_Data;
  m_Data = NULL;
}

template <typename T, int n>
bool Field<T, n>::init(int sizeX, int sizeY, int sizeZ) {
  m_RawFileName = std::string("none");
  m_Dims[0] = sizeX; m_Dims[1] = sizeY; m_Dims[2] = sizeZ;
  m_VoxelSize[0] = 1.0; m_VoxelSize[1] = 1.0; m_VoxelSize[2] = 1.0;
  m_Data = new T[n*sizeX*sizeY*sizeZ];
  return true;
}

template <typename T, int n>
void Field<T, n>::clear()
{
  m_RawFileName = std::string("none");
  m_Dims[0] = 0; m_Dims[1] = 0; m_Dims[2] = 0;
  m_VoxelSize[0] = 1.0; m_VoxelSize[1] = 1.0; m_VoxelSize[2] = 1.0;
  
  if (m_Data) delete [] m_Data;
  m_Data = NULL;
}

template <typename T, int n>
void Field<T, n>::copy(const Field<T, n>& rhs) {
  m_RawFileName = rhs.m_RawFileName;
  m_Dims[0] = rhs.m_Dims[0]; m_Dims[1] = rhs.m_Dims[1]; m_Dims[2] = rhs.m_Dims[2];
  m_VoxelSize[0] = rhs.m_VoxelSize[0];m_VoxelSize[1] = rhs.m_VoxelSize[1];m_VoxelSize[2] = rhs.m_VoxelSize[2];
  if (m_Data) delete [] m_Data;
  m_Data = new T[n*m_Dims[0]*m_Dims[1]*m_Dims[2]];
  memcpy(m_Data, rhs.getDataPtr(), n*m_Dims[0]*m_Dims[1]*m_Dims[2]*sizeof(T));
}

template <typename T, int n>
bool Field<T, n>::importRawFieldFromFile(std::string fileName) {
  FILE * pFile;
  long lSize;

  pFile = fopen ( fileName.c_str() , "rb" );
  if (pFile==NULL) 
    return false;

  // obtain file size.
  fseek (pFile , 0 , SEEK_END);
  lSize = ftell (pFile);
  rewind (pFile);

  if ( lSize != n*m_Dims[0]*m_Dims[1]*m_Dims[2]*sizeof(T) ) {
    fclose(pFile);
    return false;
  }

  // copy the file into the buffer.
  fread (m_Data, 1, lSize, pFile);

  // terminate
  fclose (pFile);

  return true;
}

template <typename T, int n>
bool Field<T, n>::exportRawFieldToFile(std::string fileName) {
  FILE * pFile;
  long lSize;

  lSize = n*m_Dims[0]*m_Dims[1]*m_Dims[2]*sizeof(T);

  pFile = fopen ( fileName.c_str() , "wb" );
  if (pFile==NULL) 
    return false;

  // write the data to file ...
  fwrite (m_Data, 1, lSize, pFile);

  // terminate
  fclose (pFile);

  return true;
}

template <typename T, int n>
bool Field<T, n>::init(std::string filename) {
  char tmp[256];
  char tmp2[256]; /* for scanning the .mhd file and getting the volume
                     dimensions */
  char temp[256]; /* a dummy string */

  // std::cout << "Opening file: " << str << std::endl;

  FILE *fp_dat;  /* pointer to the FILE structure for the corresponding .mhd
  */
  if ((fp_dat = fopen(filename.c_str(), "r")) == NULL) 
  { 
    // std::cout << "Could not open file" << std::endl;
    return false;
  } 

  char fName[256];
  char c[8];
  while (!feof(fp_dat)) 
  {
    fgets(temp, 255, fp_dat); 
    // std::cout << ">>: " << std::string(temp) << std::endl;
    if (ferror(fp_dat))
    {
      //wxLogMessage(wxT("Error reading Volume header file"));
      return false;
    } 
    if (strncmp(temp, "ElementDataFile =", strlen("ElementDataFile =")) ==
        0) 
    {
      sscanf(temp, "%s %s %s", tmp2, c, fName);
    }
    if (strncmp(temp, "DimSize =", strlen("DimSize =")) == 0) 
    {
      sscanf(temp, "%s %s %d %d %d", tmp2, c, &m_Dims[0], &m_Dims[1],
          &m_Dims[2]);
    }
    if (strncmp(temp, "ElementSpacing =", strlen("ElementSpacing =")) == 0) 
    {
      sscanf(temp, "%s %s %lf %lf %lf", tmp2, c, &m_VoxelSize[0],
          &m_VoxelSize[1], &m_VoxelSize[2]);
    }
    if (strncmp(temp, "ElementType =", strlen("ElementType =")) == 0) 
    {
      sscanf(temp, "%s %s %s", tmp2, c, tmp);
      if ( !strncmp(tmp, "MET_FLOAT", strlen("MET_FLOAT")) && ( sizeof(T) != sizeof(float) ) ) {
        return false; // crudely done to support only float or double ... can crash at runtime with wrong type ...
      } else if ( !strncmp(tmp, "MET_DOUBLE", strlen("MET_DOUBLE")) && ( sizeof(T) != sizeof(double) ) ) {
        return false; // crudely done to support only float or double ... can crash at runtime with wrong type ...
      } else {
        return false;
      }
    }
    if (strncmp(temp, "NDims =", strlen("NDims =")) == 0) 
    {
      int ndim;
      sscanf(temp, "%s %s %d", tmp2, c, &ndim);
      if (ndim != n)
        return false;
    } /*
         if (strncmp(temp, "Position =", strlen("Position =")) == 0) 
         {
         sscanf(temp, "%s %s %lf %lf %lf", tmp2, c, &m_Corner000[0],
         &m_Corner000[1], &m_Corner000[2]);
         }
         if (strncmp(temp, "Orientation =", strlen("Orientation =")) == 0) 
         {
         sscanf(temp, "%s %s %lf %lf %lf %lf %lf %lf %lf %lf %lf", tmp2,
         c, &m_OrientationX[0], &m_OrientationX[1],
         &m_OrientationX[2],
         &m_OrientationY[0], &m_OrientationY[1],
         &m_OrientationY[2], &m_OrientationZ[0], &m_OrientationZ[1],
         &m_OrientationZ[2]);
         } */
  }

  fclose(fp_dat);

  m_Data = 0;
  int numOfEntries = m_Dims[0] * m_Dims[1] * m_Dims[2];

  // allocate memory ... 
  m_Data = new T[n*m_Dims[0]*m_Dims[1]*m_Dims[2]];

  // strip the header name on the end and replace with image name.
  std::string imgname = filename;
  std::string::size_type pos = imgname.rfind ('/', 0);
  imgname = imgname.substr(0, pos);
  imgname += std::string("/");
  imgname += std::string(fName);

  return importRawFieldFromFile( imgname );

}

template <typename T, int n>
int Field<T, n>::load_data_BE(const char *fname, void *buf, int size)
{

  int i,j;
  int bytes = sizeof(T);
  unsigned char t;
  FILE *fp;
  unsigned char *buff;

  if(bytes % 2 != 0)
    {
      printf("Wrong size \n");
      exit(1);
    }


  if ((fp = fopen(fname, "rb"))==NULL) 
    {
      printf("Unable to open file :%s\n", fname);           
      exit(1);
    }
  

  if(!(buff = (unsigned char *) calloc(size*bytes,sizeof(unsigned char))))
    {
      printf("Error allocation memory \n");
      exit(1);
    }


  fread(buff,sizeof(unsigned char),size*bytes,fp);

  fclose(fp);

  for(i = 0; i < size; i++)
    {
      for(j = 0; j < bytes/2; j++)
	{
	  t = buff[i * bytes + j];
	  buff[i * bytes + j] = buff[i * bytes + bytes - 1 - j];
	  buff[i * bytes + bytes - 1 -j] = t;
	}
    }

  memcpy(buf, buff, size*bytes);

  free(buff);

  return(1);

}

template <typename T, int n>
int Field<T, n>::save_data_BE(const char *fname, void *buf, int size)
{

  int i,j;
  int bytes = sizeof(T);
  unsigned char t;
  FILE *fp;
  unsigned char *buff;

  if(bytes % 2 != 0)
    {
      printf("Wrong size \n");
      exit(1);
    }


  if ((fp = fopen(fname, "wb"))==NULL) 
    {
      printf("Unable to open file :%s\n", fname);           
      exit(1);
    }
  
  if(!(buff = (unsigned char *) calloc(size*bytes,sizeof(unsigned char))))
    {
      printf("Error allocation memory \n");
      exit(1);
    }

  memcpy(buff, buf, size*bytes);

  
  for(i = 0; i < size; i++)
    {
      for(j = 0; j < bytes/2; j++)
	{
	  t = buff[i * bytes + j];
	  buff[i * bytes + j] = buff[i * bytes + bytes - 1 - j];
	  buff[i * bytes + bytes - 1 -j] = t;
	}
    }

  fwrite(buff,sizeof(unsigned char),size*bytes,fp);

  fclose(fp);

  free(buff);


  return(1);

}
template <typename T, int n>
bool Field<T, n>::importRawFieldFromFile_BE(std::string fileName) {
  FILE * pFile;
  long lSize;

  load_data_BE(fileName.c_str(), m_Data, n*m_Dims[0]*m_Dims[1]*m_Dims[2]);

  return true;
}

template <typename T, int n>
bool Field<T, n>::exportRawFieldToFile_BE(std::string fileName) {
  FILE * pFile;
  long lSize;

  save_data_BE(fileName.c_str(), m_Data, n*m_Dims[0]*m_Dims[1]*m_Dims[2]);

  return true;
}


#endif // _DTIDROID_FIELDDRTG_H
