/*! \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 "./ExternalResult.h"
#include "../../shared/shared.h"
/*}}}*/

template <class ResultType> 
class GenericExternalResult: public ExternalResult {

	private: 
		int        id;
		char*      result_name;
		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){/*{{{*/
			_printf_("   id          : " << this->id << "\n");
			_printf_("   result_name : " << this->result_name<< "\n");
			_printf_("   step        : " << this->step << "\n");
			_printf_("   time        : " << this->time << "\n");
		}
		/*}}}*/
		void GenericWriteData(FILE* fid){/*{{{*/ 

			IssmPDouble  passiveDouble;

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

			/*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;
			result_name = NULL;
			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){/*{{{*/
			id          = 0;
			result_name = NULL;
			M           = 0;
			N           = 0;
			step        = 0;
			time        = 0;
			_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;
			value     = in_value;
			step      = in_step;
			time      = in_time;

			/*Convert enum to name*/
			EnumToStringx(&this->result_name,in_enum_type);
		}
		/*}}}*/
		GenericExternalResult(int in_id, int in_enum_type,ResultType in_value,int in_step){ /*{{{*/
			id        = in_id;
			value     = in_value;
			step      = in_step;
			time      = UNDEF;

			/*Convert enum to name*/
			EnumToStringx(&this->result_name,in_enum_type);
		}
		/*}}}*/
		GenericExternalResult(int in_id,const char* in_result_name,ResultType in_value,int in_step, IssmDouble in_time){ /*{{{*/
			id        = in_id;
			value     = in_value;
			step      = in_step;
			time      = in_time;

			/*Copy name*/
			this->result_name = xNew<char>(strlen(in_result_name)+1);
			xMemCpy<char>(this->result_name,in_result_name,strlen(in_result_name)+1);
		}
		/*}}}*/
		~GenericExternalResult(){ /*{{{*/
			xDelete<char>(result_name);
		} /*}}}*/

		/*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->result_name,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);

} /*}}}*/
char* GetResultName(void){ /*{{{*/
		char* name = xNew<char>(strlen(this->result_name)+1);
		xMemCpy<char>(name,this->result_name,strlen(this->result_name)+1);
		return name;
} /*}}}*/
int   GetStep(void){ /*{{{*/
	return this->step;
} /*}}}*/
IssmPDouble GetValue(void){ /*{{{*/
	/*Only supported by IssmPDouble result, error out by default*/
	_error_("not supported for this type of result");
	return 0.;
} /*}}}*/
};

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

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

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

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

	_printf_("GenericExternalResult<int>:\n");
	this->GenericEcho();
	_printf_("   value: " << this->value << "\n");

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

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

	_printf_("GenericExternalResult<double>:\n");
	this->GenericEcho();
	_printf_("   value: " << this->value << "\n");

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

/*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;
	value = xNew<char>(strlen(in_value)+1);
	xMemCpy<char>(value,in_value,(strlen(in_value)+1));
	step  = in_step;
	time  = in_time;

	/*Convert enum to name*/
	EnumToStringx(&this->result_name,in_enum_type);

} /*}}}*/
template <> inline GenericExternalResult<char*>::GenericExternalResult(int in_id, int in_enum_type,char* in_value,int in_step){ /*{{{*/

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

	/*Convert enum to name*/
	EnumToStringx(&this->result_name,in_enum_type);

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

	_printf_("GenericExternalResult<char*>:\n");
	this->GenericEcho();
	_printf_("   value: " << this->value << "\n");

} /*}}}*/
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;
	M  = in_M;
	N  = in_N;

	EnumToStringx(&this->result_name,in_enum_type);

	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<char>(result_name);
	xDelete<IssmPDouble>(value);
} /*}}}*/
template <> inline void GenericExternalResult<IssmPDouble*>::Echo(void){ /*{{{*/

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

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

	int i,j;

	_printf_("GenericExternalResult<IssmPDouble*>:\n");
	this->GenericEcho();

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

} /*}}}*/
template <> inline Object* GenericExternalResult<IssmPDouble*>::copy(void){ /*{{{*/
	return new GenericExternalResult<IssmPDouble*>(this->id,StringToEnumx(this->result_name),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     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: */
	int length=(strlen(this->result_name)+1)*sizeof(char);
	fwrite(&length,sizeof(int),1,fid);
	fwrite(this->result_name,length,1,fid);

	/*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;
} /*}}}*/

/*Specific instantiations for IssmDouble*: */
#if defined(_HAVE_ADOLC_) && !defined(_WRAPPERS_)  //We hook off this specific specialization when not running ADOLC, otherwise we get a redeclaration with the next specialization. 
template <> inline void GenericExternalResult<IssmDouble*>::WriteData(FILE* fid,bool io_gather){ /*{{{*/

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

	/*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: */
	int length=(strlen(this->result_name)+1)*sizeof(char);
	fwrite(&length,sizeof(int),1,fid);
	fwrite(this->result_name,length,1,fid);

	/*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);

	passiveDoubles=xNew<IssmPDouble>(this->M*this->N);
	for (i=0;i<this->M*this->N;i++)passiveDoubles[i]=reCast<IssmPDouble>(value[i]);
	fwrite(passiveDoubles,cols*rows*sizeof(IssmPDouble),1,fid);
	xDelete<IssmPDouble>(passiveDoubles);

}
/*}}}*/
#endif

/*Specifics instantiations for Vector*/
template <> inline GenericExternalResult<Vector<IssmPDouble>*>::GenericExternalResult(int in_id, int in_enum_type,Vector<IssmPDouble>* in_values, int in_M,int in_N,int in_step,IssmDouble in_time){/*{{{*/
	_error_("instanciation not correct");
}
/*}}}*/
template <> inline GenericExternalResult<Vector<IssmPDouble>*>::GenericExternalResult(int in_id, int in_enum_type,Vector<IssmPDouble>* in_value,int in_step, IssmDouble in_time){ /*{{{*/
	id = in_id;
	M  = 0;
	N  = 0;

	/*Convert enum to name*/
	EnumToStringx(&this->result_name,in_enum_type);

	step = in_step;
	time = in_time;

	value = in_value;
} /*}}}*/
template <> inline GenericExternalResult<Vector<IssmPDouble>*>::~GenericExternalResult(){ /*{{{*/
	xDelete<char>(this->result_name);
	delete value;
} /*}}}*/
template <> inline void GenericExternalResult<Vector<IssmPDouble>*>::Echo(void){ /*{{{*/

	_printf_("GenericExternalResult<Vector<IssmPDouble>*>:\n");
	this->GenericEcho();
	this->value->Echo();

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

	this->Echo();

} /*}}}*/
template <> inline Object* GenericExternalResult<Vector<IssmPDouble>*>::copy(void){ /*{{{*/
	return new GenericExternalResult<Vector<IssmPDouble>*>(this->id,StringToEnumx(this->result_name),this->value,this->step,this->time);
} /*}}}*/
#if defined(_HAVE_ADOLC_) && !defined(_WRAPPERS_)  //We hook off this specific specialization when not running ADOLC, otherwise we get a redeclaration with the next specialization. 
template <> inline void GenericExternalResult<Vector<IssmPDouble>*>::WriteData(FILE* fid,bool io_gather){ /*{{{*/

	char *name   = NULL;
	int   length,rows,cols=1;

	if(!io_gather){
		_error_("not supported yet");
	}

	/*Serialize vector*/
	IssmPDouble* serialvalues = this->value->ToMPISerial();
	this->value->GetSize(&rows);

	if(IssmComm::GetRank()==0){
		/*First write name: */
		length=(strlen(this->result_name)+1)*sizeof(char);
		fwrite(&length,sizeof(int),1,fid);
		fwrite(this->result_name,length,1,fid);

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

		/*writing a IssmDouble array, type is 3:*/
		int type=3;
		fwrite(&type,sizeof(int),1,fid);
		fwrite(&rows,sizeof(int),1,fid);
		fwrite(&cols,sizeof(int),1,fid);
		fwrite(serialvalues,cols*rows*sizeof(IssmPDouble),1,fid);
	}

	/*Clean up*/
	xDelete<IssmPDouble>(serialvalues);

}
/*}}}*/
#endif
template <> inline int GenericExternalResult<Vector<IssmPDouble>*>::ObjectEnum(void){ /*{{{*/
	return NoneEnum;
	/*???? FIXME*/
} /*}}}*/

/*Specifics instantiations for Vector<IssmDouble>*/
template <> inline void GenericExternalResult<Vector<IssmDouble>*>::WriteData(FILE* fid,bool io_gather){ /*{{{*/

	int i;
	char *name   = NULL;
	int   length,rows,cols=1;
	IssmDouble*  serialvalues = NULL;
	IssmPDouble* pserialvalues = NULL;

	if(!io_gather){
		_error_("not supported yet");
	}

	/*Serialize vector*/
	serialvalues = this->value->ToMPISerial();
	this->value->GetSize(&rows);
	
	pserialvalues=xNew<IssmPDouble>(rows);
	for(i=0;i<rows;i++)pserialvalues[i]=reCast<IssmPDouble>(serialvalues[i]);

	if(IssmComm::GetRank()==0){
		/*First write name: */
		length=(strlen(this->result_name)+1)*sizeof(char);
		fwrite(&length,sizeof(int),1,fid);
		fwrite(this->result_name,length,1,fid);

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

		/*writing a IssmDouble array, type is 3:*/
		int type=3;
		fwrite(&type,sizeof(int),1,fid);
		fwrite(&rows,sizeof(int),1,fid);
		fwrite(&cols,sizeof(int),1,fid);
		fwrite(pserialvalues,cols*rows*sizeof(IssmPDouble),1,fid);
	}

	/*Clean up*/
	xDelete<IssmPDouble>(pserialvalues);
	xDelete<IssmDouble>(serialvalues);

}
/*}}}*/

#endif  /* _EXTERNAL_RESULTOBJECT_H */
