/*! \file GenericExternalResult.h 
 *  \brief: header file for generic external result object
 */

#ifndef _GENERIC_EXTERNAL_RESULT_
#define _GENERIC_EXTERNAL_RESULT_

/*Headers:{{{*/
#ifdef HAVE_CONFIG_H
	#include <config.h>
#else
#error "Cannot compile with HAVE_CONFIG_H symbol! run configure first!"
#endif

#include <cstring>

#include "../../../include/include.h"
#include "../../../shared/Exceptions/exceptions.h"
#include "../../../shared/Numerics/recast.h"
#include "../../../io/io.h"
#include "../../../EnumDefinitions/EnumDefinitions.h"
#include "../../../classes/IssmComm.h"
#include "./ExternalResult.h"
/*}}}*/

template <class ResultType> 
class GenericExternalResult: public ExternalResult {

	private: 
		int id;
		int enum_type;
		ResultType value;
		int M;
		int N;
		int step;
		IssmDouble time;

	public:
		/*Diverse: must be in front, as it is used in what follows*/
		void GenericEcho(void){/*{{{*/
			_printLine_("   id: " << this->id);
			_printLine_("   enum:  " << this->enum_type << " (" << EnumToStringx(this->enum_type) << ")");
			_printLine_("   step: " << this->step);
			_printLine_("   time: " << this->time);
		}
		/*}}}*/
		void GenericWriteData(FILE* fid){/*{{{*/ 

			int     length;
			IssmPDouble  passiveDouble;
			char*   name = NULL;

			/*First write enum: */
			EnumToStringx(&name,this->enum_type);
			length=(strlen(name)+1)*sizeof(char);
			fwrite(&length,sizeof(int),1,fid);
			fwrite(name,length,1,fid);
			xDelete<char>(name);

			/*Now write time and step: */
			passiveDouble=reCast<IssmPDouble>(time);
			fwrite(&passiveDouble,sizeof(IssmPDouble),1,fid);
			fwrite(&step,sizeof(int),1,fid);
		} /*}}}*/

		/*GenericExternalResult constructors and  destructors*/
		GenericExternalResult(){ /*{{{*/
			id  = 0;
			enum_type = NoneEnum;
			M=0;
			N=0;
			step=0;
			time=0;
		} /*}}}*/
		GenericExternalResult(int in_id, int in_enum_type,ResultType in_values, int in_M,int in_N,int in_step,IssmDouble in_time){/*{{{*/
			_error_("template GenericExternalResult(int in_id, int in_enum_type,double* in_values, int in_M,int in_N,int in_step,IssmDouble in_time) not implemented for this ResultType\n");
		}
/*}}}*/
		GenericExternalResult(int in_id, int in_enum_type,ResultType in_value,int in_step, IssmDouble in_time){ /*{{{*/
			id=in_id;
			enum_type=in_enum_type;
			value=in_value;
			step=in_step;
			time=in_time;
		}
		/*}}}*/
		~GenericExternalResult(){ /*{{{*/
		} /*}}}*/

		/*Object virtual functions definitions:*/
		void Echo(void){ /*{{{*/
			this->DeepEcho();
		}
		/*}}}*/
		void DeepEcho(void){ /*{{{*/
			_error_("template DeepEcho not implemented for this ResultType\n");
		}
		/*}}}*/
		int Id(void){ /*{{{*/ 
			return -1; 
		} /*}}}*/
		int ObjectEnum(void){ /*{{{*/
			_error_("template ObjectEnum not implemented for this ResultType\n");
		} /*}}}*/
		Object* copy(void) { /*{{{*/
			return new GenericExternalResult<ResultType>(this->id,this->enum_type,this->value,this->step,this->time);
		} /*}}}*/

		/*GenericExternalResult management: */
void WriteData(FILE* fid,bool io_gather){ /*{{{*/

	int     my_rank;
	int     type;
	int     size;
	IssmPDouble  passiveDouble;

	/*recover my_rank:*/
	my_rank=IssmComm::GetRank();

	/*return if now on cpu 0: */
	if(my_rank)return;

	/*use generic part, same for all ResultTypes: */
	this->GenericWriteData(fid);

	/*writing a IssmPDouble for Matlab or Python to post-process, type is 1, size is 1: */
	type=1;
	size=1;
	fwrite(&type,sizeof(int),1,fid);
	fwrite(&size,sizeof(int),1,fid);

	/*cast to a IssmPDouble: */
	passiveDouble=reCast<IssmPDouble>(value);
	fwrite(&passiveDouble,size*sizeof(IssmPDouble),1,fid);

} /*}}}*/
void GetResultName(char** pname){ /*{{{*/
	EnumToStringx(pname,this->enum_type);
} /*}}}*/
int GetStep(void){ /*{{{*/
	return this->step;
} /*}}}*/
int InstanceEnum(void){ /*{{{*/
	return this->enum_type;
} /*}}}*/
};

/*Specific instantiations for bool: */
template <> inline void GenericExternalResult<bool>::DeepEcho(void){ /*{{{*/

	_printLine_("GenericExternalResult<bool>:");
	this->GenericEcho();
	_printLine_("   value: " <<(this->value?"true":"false"));

} /*}}}*/
template <> inline int GenericExternalResult<bool>::ObjectEnum(void){ /*{{{*/
	return BoolExternalResultEnum;
} /*}}}*/

/*Specific instantiations for int: */
template <> inline void GenericExternalResult<int>::DeepEcho(void){ /*{{{*/

	_printLine_("GenericExternalResult<int>:");
	this->GenericEcho();
	_printLine_("   value: " << this->value);

} /*}}}*/
template <> inline int GenericExternalResult<int>::ObjectEnum(void){ /*{{{*/
	return IntExternalResultEnum;
} /*}}}*/

/*Specific instantiations for double: */
template <> inline void GenericExternalResult<double>::DeepEcho(void){ /*{{{*/

	_printLine_("GenericExternalResult<double>:");
	this->GenericEcho();
	_printLine_("   value: " << this->value);

} /*}}}*/
template <> inline int GenericExternalResult<double>::ObjectEnum(void){ /*{{{*/
	return DoubleExternalResultEnum;
} /*}}}*/

/*Specific instantiations for char*: */
template <> inline GenericExternalResult<char*>::GenericExternalResult(int in_id, int in_enum_type,char* in_value,int in_step, IssmDouble in_time){ /*{{{*/

	id=in_id;
	enum_type=in_enum_type;
	value=xNew<char>(strlen(in_value)+1);
	xMemCpy<char>(value,in_value,(strlen(in_value)+1));
	step=in_step;
	time=in_time;

} /*}}}*/
template <> inline GenericExternalResult<char*>::~GenericExternalResult(){ /*{{{*/
	xDelete<char>(value);
} /*}}}*/
template <> inline void GenericExternalResult<char*>::DeepEcho(void){ /*{{{*/

	_printLine_("GenericExternalResult<char*>:");
	this->GenericEcho();
	_printLine_("   value: " << this->value);

} /*}}}*/
template <> inline void GenericExternalResult<char*>::WriteData(FILE* fid,bool io_gather){ /*{{{*/

	int     my_rank;
	int     type;
	int     length;

	/*recover my_rank:*/
	my_rank=IssmComm::GetRank();

	/*return if now on cpu 0: */
	if(my_rank)return;

	/*use generic part, same for all ResultTypes: */
	this->GenericWriteData(fid);

	/*writing a string, type is 2: */
	type=2;
	fwrite(&type,sizeof(int),1,fid);

	length=(strlen(this->value)+1)*sizeof(char);
	fwrite(&length,sizeof(int),1,fid);
	fwrite(this->value,length,1,fid);
}
/*}}}*/
template <> inline int GenericExternalResult<char*>::ObjectEnum(void){ /*{{{*/
	return StringExternalResultEnum;
} /*}}}*/

/*Specific instantiations for IssmPDouble*: */
template <> inline GenericExternalResult<IssmPDouble*>::GenericExternalResult(int in_id, int in_enum_type,IssmPDouble* in_values, int in_M,int in_N,int in_step,IssmDouble in_time){/*{{{*/

	id=in_id;
	enum_type=in_enum_type;
	M=in_M;
	N=in_N;

	step=in_step;
	time=in_time;

	/*Copy result in values*/
	if(M*N){
		value=xNew<IssmPDouble>(M*N);
		xMemCpy<IssmPDouble>(value,in_values,M*N);
	}
	else value=NULL;
}
/*}}}*/
template <> inline GenericExternalResult<IssmPDouble*>::GenericExternalResult(int in_id, int in_enum_type,IssmPDouble* in_value,int in_step, IssmDouble in_time){ /*{{{*/
	_error_("you cannot initialize a GenericExternalResult<IssmPDouble*> without providing the dimensions of the matrix! Please use a more appropriate constructor!");
} /*}}}*/
template <> inline GenericExternalResult<IssmPDouble*>::~GenericExternalResult(){ /*{{{*/
	xDelete<IssmPDouble>(value);
} /*}}}*/
template <> inline void GenericExternalResult<IssmPDouble*>::Echo(void){ /*{{{*/

	_printLine_("GenericExternalResult<IssmPDouble*>:");
	this->GenericEcho();
	_printLine_("   matrix size: " << this->M << "-" << this->N);

} /*}}}*/
template <> inline void GenericExternalResult<IssmPDouble*>::DeepEcho(void){ /*{{{*/

	int i,j;

	_printLine_("GenericExternalResult<IssmPDouble*>:");
	this->GenericEcho();

	_printLine_("   matrix size: " << this->M << "-" << this->N);
	for (i=0;i<this->M;i++){  
		_printString_("   [ ");
		for (j=0;j<this->N;j++){
			_printString_( " " << setw(11) << setprecision (5) << this->value[i*this->N+j]);
		}  
		_printLine_(" ]");
	}  

} /*}}}*/
template <> inline Object* GenericExternalResult<IssmPDouble*>::copy(void){ /*{{{*/
	return new GenericExternalResult<IssmPDouble*>(this->id,this->enum_type,this->value,this->M,this->N,this->step,this->time);
} /*}}}*/
template <> inline void GenericExternalResult<IssmPDouble*>::WriteData(FILE* fid,bool io_gather){ /*{{{*/

	int     my_rank;
	int     length;
	int     type;
	int     rows,cols;
	char   *name    = NULL;
	IssmPDouble passiveDouble;

	/*recover my_rank:*/
	my_rank=IssmComm::GetRank();

	if(io_gather){
		/*we are gathering the data on cpu 0, don't write on other cpus: */
		if(my_rank) return;
	}

	/*First write enum: */
	EnumToStringx(&name,this->enum_type);
	length=(strlen(name)+1)*sizeof(char);
	fwrite(&length,sizeof(int),1,fid);
	fwrite(name,length,1,fid);
	xDelete<char>(name);

	/*Now write time and step: */
	passiveDouble=reCast<IssmPDouble>(time);
	fwrite(&passiveDouble,sizeof(IssmPDouble),1,fid);
	fwrite(&step,sizeof(int),1,fid);

	/*writing a IssmDouble array, type is 3:*/
	type=3;
	fwrite(&type,sizeof(int),1,fid);
	rows=this->M;
	fwrite(&rows,sizeof(int),1,fid);
	cols=this->N;
	fwrite(&cols,sizeof(int),1,fid);
	fwrite(value,cols*rows*sizeof(IssmPDouble),1,fid);

}
/*}}}*/
template <> inline int GenericExternalResult<IssmPDouble*>::ObjectEnum(void){ /*{{{*/
	return DoubleMatExternalResultEnum;
} /*}}}*/

#endif  /* _EXTERNAL_RESULTOBJECT_H */
