/* ImLib3D
 * Copyright (c) 2001, ULP-IPB Strasbourg.
 *
 * This program 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 2 of the License, or (at
 * your option) any later version.
 * 
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*! \file  FileConversion.hpp*/

#ifndef _FileConversion_hpp
#define _FileConversion_hpp
#include<ImLib3D/ImageProcessor.hpp>
#include<ImLib3D/FFT.hpp>
#include<ImLib3D/ConvenienceProcessors.hpp>

#include<avwio/avwio.h>

namespace FileConversion 
{
	inline string AVWImLib3DImageType(string fname)
	{
		string typeName;
		AVW *avw=AvwOpen( fname.c_str(), "r" ); 
		if(!avw){ThrowError("CReadFromFileAVW:: open failed for \"%s\"",fname);}
		short dataType;
		AvwGetDataType ( avw, &dataType ); 
		if(dataType==DT_UNSIGNED_CHAR ){typeName=TypeName<Mask3D>();}
		if(dataType==DT_SIGNED_SHORT  ){typeName=TypeName<Image3Dreal<short int> >();}
		if(dataType==DT_SIGNED_INT    ){typeName=TypeName<LabelImage3D>();}
		if(dataType==DT_FLOAT         ){typeName=TypeName<Image3Df>();}
		if(dataType==DT_COMPLEX       ){typeName=TypeName<Image3Dcomplexd>();}
		if(dataType==DT_DOUBLE        ){typeName=TypeName<Image3Dd>();}
		AvwClose ( avw ); 
		return typeName;
	}
	
	template <class TTT> 
	           inline short AVWType                   (){return DT_UNKNOWN        ;}
	template<> inline short AVWType<byte             >(){return DT_UNSIGNED_CHAR  ;}
	template<> inline short AVWType<short            >(){return DT_SIGNED_SHORT   ;}
	template<> inline short AVWType<int              >(){return DT_SIGNED_INT     ;}
	template<> inline short AVWType<float            >(){return DT_FLOAT          ;}
	template<> inline short AVWType<complex<double>  >(){return DT_COMPLEX        ;}
	template<> inline short AVWType<double           >(){return DT_DOUBLE         ;}
	template <class TTT> 
                struct IsAVWType                    {typedef __false_type type;};
	template <> struct IsAVWType<byte             > {typedef __true_type type;};
	template <> struct IsAVWType<short            > {typedef __true_type type;};
	template <> struct IsAVWType<int              > {typedef __true_type type;};
	template <> struct IsAVWType<float            > {typedef __true_type type;};
	template <> struct IsAVWType<complex<double>  > {typedef __true_type type;};
	template <> struct IsAVWType<double           > {typedef __true_type type;};
//	short AVWType<DT_BINARY         >(){return DT_BINARY         ;}
//	short AVWType<DT_RGB            >(){return DT_RGB            ;}

};



namespace IP3D
{
/*! #&ImageFromRawData&#     Create an ImLib3D image from raw data.
        The image type determines the raw data type (for example Image3Df will 
        assume data is float). The byte ordering in the raw data is given by a 
        three letter string (ex."ZXY").  
        Example: 
        imlib3d ImageFromRawData "Image3Dreal<short int>" rare_cra.img 128 128 128 
        ZXY res.im3D true 
        im ImageTypeConversion res.im3D res1.im3D Image3Df 
        imlib3dview  res1.im3D 
     <UL>Args:
      <LI>Result typename    type:ImageTypeName
      <LI>Raw data           type:raw data
      <LI>Image size         type:Size3D
      <LI>Raw data ordering  type:string
      <LI>Result image       type:ImageType
      <LI>ReverseEndian      type:bool
     </UL>
~*/
	template <class ImageType>
	void ImageFromRawData(const typename ImageType::value_type *data,const Size3D &size,string order,ImageType &res,bool reverseEndian=false)
	{
		typedef typename ImageType::value_type Im3DValue;
		res.Resize(size);
		Vect3Di vorder;
		for(int i=0;i<3;i++)
		{
			vorder(i)=order[i]-'X';
			if(vorder(i)<0 || vorder(i)>2){ThrowError("CImageFromRawData::bad order string");}
		}
		Vect3Di fact;
		Vect3Di vsize=size.GetV();
		fact(vorder(0))=1;
		fact(vorder(1))=vsize(vorder(0));
		fact(vorder(2))=vsize(vorder(0))*vsize(vorder(1));

		for(typename ImageType::iteratorXYZ p=res.begin();p!=res.end();++p)
		{
			const Im3DValue &v=data[p.x*fact.x+p.y*fact.y+p.z*fact.z];
			if(!reverseEndian){*p=v;}
			else{ReverseEndian(v,*p);}			
		}
	}
};
namespace IP3D
{
/*! #&RawDataFromImage&#     Create an raw binary data from an image.
        The image type determines the raw data type (for example Image3Df will 
        assume data is float). The byte ordering in the raw data is given by a 
        three letter string (ex."ZXY").  
     <UL>Args:
      <LI>Source image       type:ImageType
      <LI>Raw data ordering  type:string
      <LI>Raw data           type:data
      <LI>ReverseEndian      type:bool
     </UL>
~*/
	template <class ImageType>
	void RawDataFromImage(const ImageType &src,string order,typename ImageType::value_type **pres,bool reverseEndian=false)
	{
		typedef typename ImageType::value_type Im3DValue;
		*pres=new Im3DValue[src.GetNVoxels()];
		Im3DValue *res=*pres;
		Vect3Di vorder;
		for(int i=0;i<3;i++)
		{
			vorder(i)=order[i]-'X';
			if(vorder(i)<0 || vorder(i)>2){ThrowError("CRawDataFromImage::bad order string");}
		}
		Vect3Di fact;
		Vect3Di vsize=src.SizeV();
		fact(vorder(0))=1;
		fact(vorder(1))=vsize(vorder(0));
		fact(vorder(2))=vsize(vorder(0))*vsize(vorder(1));

		for(typename ImageType::const_iteratorXYZ p=src.begin();p!=src.end();++p)
		{
			Im3DValue v;
			if(!reverseEndian){v=*p;}
			else{ReverseEndian(*p,v);}			
			res[p.x*fact.x+p.y*fact.y+p.z*fact.z]=v;
		}
	}
};


namespace IP3D
{
/*! #&WriteToFileAVW&#     Write an image in AVW/Analyze format.
     <UL>Args:
      <LI>Source image   type:ImageType
      <LI>Filename Base  type:string
     </UL>
~*/
	template <class ImageType >
	void WriteToFileAVW(const ImageType &src,const string &fname)
	{
		using namespace FileConversion;
		if(IsSameType<__false_type,typename IsAVWType<typename ImageType::value_type>::type>())
		{
			ThrowError("IP3D::WriteToFileAVW ImageType:\"%s\" can not be converted to AVW",TypeName<ImageType>());
		}
		AVW *avw=AvwOpen( fname.c_str(), "w" ); 
		if(!avw){ThrowError("CWriteToFileAVW:: open failed for \"%s\"",fname);}
		AvwInitHeader ( avw, FileConversion::AVWType<typename ImageType::value_type>(), 
						src.Width(), src.Height(), src.Width(), 1, 
						1,1,1, 0, // dx,dy,dz,dt
						3, "mm" ); 
		size_t nVolsRes=AvwWriteVolumes (avw, (const void *)src.GetData(), 1 ); 
		printf("CWriteToFileAVW:: wrote %d volumes\n",nVolsRes);
		AvwClose ( avw ); 
	}
};

namespace IP3D
{
/*! #&ReadFromFileAVW&#     Read an image in AVW/Analyze format.
     <UL>Args:
      <LI>Filename Base  type:string
      <LI>Image          type:ImageType
     </UL>
~*/
	template <class ImageType >
	void ReadFromFileAVW(const string &fname,ImageType &res)
	{
		using namespace FileConversion;
		AVW *avw=AvwOpen( fname.c_str(), "r" ); 
		if(!avw){ThrowError("CReadFromFileAVW:: open failed for \"%s\"",fname);}
		short w,h,d,v;
		short dataType;
		AvwGetDataType ( avw, &dataType ); 
		if(dataType!=FileConversion::AVWType<typename ImageType::value_type>())
		{
				ThrowError("CReadFromFileAVW:: template image data type:%d does not match data type in avw file:%d",
						 FileConversion::AVWType<typename ImageType::value_type>(),dataType);
		}
		AvwGetDim ( avw,&w,&h,&d,&v ); 
		char orientation[1000];
		AvwGetOrientation ( avw, orientation ); 
		printf("CReadFromFileAVW:: image size:%d %d %d nvol:%d  orientation:\"%s\"\n",w,h,d,v,orientation);
		res.Resize(Size3D(w,h,d));
		size_t nVolsRes=AvwReadVolumes (avw, (void *)res.GetData(), 1 ); 
		printf("CReadFromFileAVW:: read %d volumes\n",nVolsRes);
		AvwClose ( avw ); 
	}
};

namespace IP3D
{
/*! #&ReadFromFileBruker&#     Dirty hack for reading an image in brucker format.
        This probably wont work for you...
     <UL>Args:
      <LI>Filename Base  type:string
      <LI>Image number   type:int
      <LI>Image          type:Image3Dreal<short int>
     </UL>
~*/
		void ReadFromFileBruker(const string &fname,int imageNumber,Image3Dreal<short int> &res);
};

namespace IP3D
{
/*! #&ExportImageSlice&#     Save a slice of an image in ppm format
        Takes a slice in a given direction (0=yz 1=xy 2=xy) and position and saves 
        it in ppm format.  
        If image was floating point, it is rescaled.
     <UL>Args:
      <LI>src        type:ImageType
      <LI>direction  type:int
      <LI>position   type:int
      <LI>filename   type:string
     </UL>
~*/
	template <class ImageType>
	void ExportImageSlice(const ImageType &src,int direction,int position,const string &fname)
	{
		typename ImageType::value_type minv,maxv;
		if(IsFloat<typename ImageType::value_type>())
		{
			IP3D::FindMinMax(src,minv,maxv);
		}
		ImageType croped;
		Vect3Di p0(0,0,0);
		p0(direction)=position;
		Vect3Di p1=src.SizeV()-Vect3Di(1,1,1);
		p1(direction)=position;
		IP3D::Crop(src,RectZone3Di(p0,p1),croped);
		int c1=(direction+1)%3;
		int c2=(direction+2)%3;
		int width=src.SizeV()(c1);
		int height=src.SizeV()(c2);
		typedef byte ppmtype;
		ppmtype *res=new ppmtype(width*height*3);
		uint maxvalue=(1<<(8*sizeof(ppmtype)))-1;
		for(typename ImageType::iteratorXYZ p=croped.begin();p!=croped.end();++p)
		{
			ppmtype v;
			if(IsFloat<typename ImageType::value_type>())
			{				
				v=(ppmtype) ((((*p)-minv)/(maxv-minv))*maxvalue);
			}
			else{v=(ppmtype)(*p);}
			res[(p.Pos()(c1)+p.Pos()(c2)*width)*3+0]=v;
			res[(p.Pos()(c1)+p.Pos()(c2)*width)*3+1]=v;
			res[(p.Pos()(c1)+p.Pos()(c2)*width)*3+2]=v;
		}
		FILE *F=fopen(fname.c_str(),"w");
		if(!F){ThrowError("IP3D::ExportImageSlice output file \"%s\" open failed",fname);}
		fprintf(F,"P6\n%d %d\n%d\n",width,height,maxvalue);
		fwrite((void *)res,width*height*3*sizeof(ppmtype),1,F);
		fclose(F);
		delete [] res;
	}
};

#endif// _FileConversion_hpp
