/**---------------------------------------------------------------------------
 ** 
 ** convert.c -- Conversion of NBR data
 ** 
 ** Author          : Pierre Jaccard
 ** Created On      : 1999/07/15 09:32:47
 ** Last Modified By: Pierre Jaccard
 ** Last Modified On: 1999/07/16 21:15:19
 ** Update Count    : 4
 ** Directory       : /home/pego/pcd1/codas3c/gfi/src/libs/nbr/
 ** Version         : 0.0
 ** Status          : Unknown
 ** ---------------------------------------------------------------------- ** 
 ** DESCRIPTION: 
 ** 
 **    This file provides functions for converting NBR data into more
 **    convenient variable types and physical units.
 ** 
 ** ---------------------------------------------------------------------- ** 
 ** REVISIONS: 
 ** ---------------------------------------------------------------------- ** 
 ** CHANGES: 
 **------------------------------------------------------------------------**/

#include "convert.h"

/* ------------------------------------------------------------------
   The following list contains all velocity scaling factors for all kinds of
   VM-ADCP configurations
   ------------------------------------------------------------------ */
static NBR_VELOCITY_SCALE_TYPE velocity_scale_list[]=
{
	/* low range - beam coordinate system */
	{ 0x73, 0x00, 0.25  },  /*   75 kHz */
	{ 0x73, 0x10, 0.125 },  /*  150 kHz */
	{ 0x73, 0x20, 0.125 },  /*  300 kHz */
	{ 0x73, 0x30, 0.125 },  /*  600 kHz */
	{ 0x73, 0x40, 0.125 },  /* 1200 kHz */
	/* low range - earth coordinate system */
	{ 0x73, 0x02, 0.5   },  /*   75 kHz */
	{ 0x73, 0x12, 0.25  },  /*  150 kHz */
	{ 0x73, 0x22, 0.25  },  /*  300 kHz */
	{ 0x73, 0x32, 0.25  },  /*  600 kHz */
	{ 0x73, 0x42, 0.25  },  /* 1200 kHz */
	/* high range - beam coordinate system */
	{ 0x73, 0x01, 0.25  },  /*   75 kHz */
	{ 0x73, 0x11, 0.25  },  /*  150 kHz */
	{ 0x73, 0x21, 0.25  },  /*  300 kHz */
	{ 0x73, 0x31, 0.25  },  /*  600 kHz */
	{ 0x73, 0x41, 0.25  },  /* 1200 kHz */
	/* high range - earth coordinate system */
	{ 0x73, 0x03, 0.5   },  /*   75 kHz */
	{ 0x73, 0x13, 0.5   },  /*  150 kHz */
	{ 0x73, 0x23, 0.5   },  /*  300 kHz */
	{ 0x73, 0x33, 0.5   },  /*  600 kHz */
	{ 0x73, 0x43, 0.5   },  /* 1200 kHz */
	/* end of list */
	{ 0x00, 0x00, 0.0}
};

/* ------------------------------------------------------------------
	 This function fills an YHDHMS time structure from the data in an ADCP real
	 time clock structure. Additionnally, a year has also to be given to get a 
	 complete YHDHMS time.

   RETURNS: 0
	 ------------------------------------------------------------------ */ 
#if PROTOTYPE_ALLOWED
int rtc_to_ymdhms(/* Pointer to ADCP time structure */
                  NBR_RTC_TYPE *rtc, 
                  /* Pointer to time structure */
                  YMDHMS_TIME_TYPE *dbt, 
                  /* Current year */
                  int year)
#else
int rtc_to_ymdhms(rtc, dbt, year) 
     NBR_RTC_TYPE *rtc;
     YMDHMS_TIME_TYPE *dbt;
     int year;
#endif
{

	dbt->year=(USHORT) year;
	dbt->month=(USHORT) rtc->month;
	dbt->day=(USHORT) rtc->day;
	dbt->hour=(USHORT) rtc->hour;
	dbt->minute=(USHORT) rtc->minute;
	dbt->second=(USHORT) rtc->second;

	return(0);
}

/* ------------------------------------------------------------------
	 Given a pointer to an ADCP real time clock structure and a year (which is
	 not saved by the ADCP itself), this function returns the current time in
	 decimal days.

   RETURNS: ADCP time in decimal days
	 ------------------------------------------------------------------ */
#if PROTOTYPE_ALLOWED
double rtc_to_yd(/* Pointer to ADCP time structure */
                 NBR_RTC_TYPE *rtc, 
                 /* Currrent year */
                 int year)
#else
double rtc_to_yd(rtc, year) 
     NBR_RTC_TYPE *rtc;
     int year;
#endif
{

	YMDHMS_TIME_TYPE dbt;
	double day;

	/* Convert to normal ymdhms type */
	rtc_to_ymdhms(rtc, &dbt, year);
	/* Convert to decimal day */
	day = year_day(&dbt, year);

	return(day);

}

/* ------------------------------------------------------------------
	 Given two pointers to ADCP real time clock structures this function returns
	 the time difference in seconds of t2-t1. Since it uses conversion functions
	 to YHDHMS times, the year for each of the ADCP RTC times has also to be
	 given.

   RETURNS: time difference in seconds from start to stop time
	 ------------------------------------------------------------------ */
#if PROTOTYPE_ALLOWED
LONG rtc_diff(/* ADCP start time */
              NBR_RTC_TYPE *rtc1, 
              /* Year for start time */
              int y1, 
              /* ADCP stop time */
              NBR_RTC_TYPE *rtc2, 
              /* Year for stop time */
              int y2)
#else
LONG rtc_diff(rtc1, y1, rtc2, y2) 
     NBR_RTC_TYPE *rtc1;
     int y1;
     NBR_RTC_TYPE *rtc2;
     int y2;
#endif
{

	YMDHMS_TIME_TYPE time1, time2;
	LONG sec;

	/* Calculate both ymdhms times */
	rtc_to_ymdhms(rtc1, &time1, y1);
	rtc_to_ymdhms(rtc2, &time2, y2);

	/* Calculate difference in seconds */
	sec = TIMDIF( &time1, &time2 );

	return(sec);

}

/* ------------------------------------------------------------------
	 This function converts n bytes from src into decimal value and put them in
	 dst. This function is necessary for reaading ADCP time from raw files.

   RETURNS: CONVERT_ERROR if the number of converted bytes is not equal to
            that given on input, 0 else.
	 ------------------------------------------------------------------ */
#if PROTOTYPE_ALLOWED
int byte_hexa_to_dec(/* Array for decimal values */
                     UBYTE *dst, 
                     /* Array of hexa values */
                     UBYTE *src, 
                     /* Number of bytes to convert */
                     int n)
#else
int byte_hexa_to_dec(dst, src, n)
     UBYTE *dst;
     UBYTE *src;
     int n;
#endif
{

	UBYTE msn, lsn;
	int   i;

	/* Start conversion */
	for(i=0; i<n; i++){
		/* Get MS nibble */
		msn = *(src+i) & 0xF0;
		msn >>= 4;
		/* Get LS nibble */
		lsn = *(src+i) & 0x0F;
		/* Build the new number */
		*(dst+i) = msn*10 + lsn;
	}

  if(i != n) return(CONVERT_ERROR);
	return(0);
}

/* ------------------------------------------------------------------
	 This is the inverse function of byte_hexa_to_dec().

   RETURNS: CONVERT_ERROR if the number of converted bytes is not equal to
            that given on input, 0 else.
	 ------------------------------------------------------------------ */
#if PROTOTYPE_ALLOWED
int byte_dec_to_hexa(/* Array for hexa values */
                     UBYTE *dst, 
                     /* Array for decimal values */
                     UBYTE *src, 
                     /* Number of bytes to convert */
                     int n)
#else
int byte_dec_to_hexa(dst, src, n)
     UBYTE *dst;
     UBYTE *src;
     int n;
#endif
{

	UBYTE msn, lsn;
	int   i;

	/* Start conversion */
	for(i=0; i<n; i++){
		/* Build MS nibble */
		msn = (*(src+i) / 10) & 0x0F;
		/* Build LS nibble */
		lsn = (*(src+i) % 10) & 0x0F;
		/* Build the new number */
		*(dst+i) = msn*16 + lsn;
	}

  if(i != n) return(CONVERT_ERROR);
	return(0);
}

/* ------------------------------------------------------------------
	 Navigation Conversions:

	 Roll and pitch signs are calculated according to the ADCP Technical
	 Manual, chapter 4 and appendix C. The considered configurations are the
	 following:
	  
                         +x direction      +y direction
	 upward   convexe      beam #1           beam #3
	 downward convexe      beam #2           beam #3
	 upward   concave      beam #1           beam #3
   downward concave      beam #2           beam #3

	 The standard signs are those used by SeaPath, i.e.

	 pitch > 0 :  +y direction higher than -y direction
	 roll  > 0 :  +x direction lower than -x direction

	 For a boat aligned with the +y direction, tilts are positive for <bow_up>
	 and <port_up>.

	 This and information from the ADCP manual yield the signs values.
   ------------------------------------------------------------------ */

/* ------------------------------------------------------------------
	 Depending on the ADCP configuration, sign for roll and pitch are different
	 than those in the raw files.

   RETURNS: sign for the roll tilt angle.
	 ------------------------------------------------------------------ */
#if PROTOTYPE_ALLOWED
int roll_sign(/* ADCP configuration flags */
              UBYTE adcp_config)
#else
     int roll_sign(adcp_config)
     UBYTE adcp_config;
#endif
{

	BYTE down, up;

	/* Determine ADCP disposition */
	down = (adcp_config & 0x4) ? 1 : 0;
	up   = !down;

	/* Determine sign */
	if(up)   return(-1);
	if(down) return(-1);

	return(0);

}

/* ------------------------------------------------------------------
	 Depending on the ADCP configuration, sign for pitch and pitch are different
	 than those in the raw files. 

   RETURNS: sign for the pitch tilt angle.
	 ------------------------------------------------------------------ */
#if PROTOTYPE_ALLOWED
int pitch_sign(/* ADCP configuration flags */
               UBYTE adcp_config)
#else
     int pitch_sign(adcp_config)
     UBYTE adcp_config;
#endif
{

	BYTE down, up;

	/* Determine ADCP disposition */
	down = (adcp_config & 0x4) ? 1 : 0;
	up   = !down;

	/* Determine sign */
	if(up)   return(-1);
	if(down) return(+1);

	return(0);

}

/* ------------------------------------------------------------------
   This function returns the scale for velocity data corresponding to the ADCP
   configuration byte. This scale is used to convert NB raw velocity data from
   counts into cm/s.

   RETURNS: velocity scaling factor, or 0.0 on error.
   ------------------------------------------------------------------ */
#if PROTOTYPE_ALLOWED
double velocity_scale(/* ADCP configuration flags */
                      UBYTE adcp_config)
#else
     double velocity_scale(adcp_config)
     UBYTE adcp_config;
#endif
{

	char func[]="velocity_scale";
 
 	NBR_VELOCITY_SCALE_TYPE *vs;
 	UBYTE r;
 	
 	/* Check for validity of ADCP configuration byte */
 	if(!(adcp_config & 0x80)){
 		error_msg(func, "ADCP configuartion byte does not contain valid data");
 		return(0.0);
 	}
 	
	vs = velocity_scale_list;
	while(vs->and_mask){
		/* Process the ADCP configuration byte and return the corresponding
			 velocity scale */
		r = ((adcp_config & vs->and_mask) ^ vs->xor_mask);
		if(!r)
			return(vs->scale);
		/* Update pointer */
		vs++;
	}
	
	/* It is an error if the next lines are reached */
	error_msg(func, "No velocity scale defined for this ADCP configuration");
		return(0.0);
	
}

/* ------------------------------------------------------------------
   This function returns the scale for spectral width data corresponding to
   the ADCP configuration byte. This scale is used to convert NB raw spectral
   width data from counts into cm/s. 

   RETURNS: spectral width scaling factor, or 0.0 on error.
	 ------------------------------------------------------------------ */
#if PROTOTYPE_ALLOWED
double spectral_width_scale(/* ADCP configuration flags */
                            UBYTE adcp_config)
#else
     double spectral_width_scale(adcp_config)
     UBYTE adcp_config;
#endif
{

	double vs;
	
	/* Get the scale for velocity */
	vs = velocity_scale(adcp_config);
	
	return(2.0 * vs);
	
}

/* ------------------------------------------------------------------
	 This function returns the factor and offset parameters
	 corresponding to the NB ADCP raw variable decsribed by the value of
	 code. It is necessary to pass the current ADCP configuration byte in
	 order to get scaling parameters for velocities and spectral width.

   RETURNS: 1 one error, 0 if success. On output, the factor and offset
            parameters are updated with the associated conversion values. 
	 ------------------------------------------------------------------ */
#if PROTOTYPE_ALLOWED
int get_nbr_cnv_parameters(/* Pointer to scaling factor */
                           double *factor, 
                           /* Pointer to scaling offset */
                           double *offset, 
                           /* Code to specify the conversion type */
                           int code,
                           /* List of conversion control parameters */
													 NBR_CNV_CONTROL_TYPE *cnv_control)
#else
     int get_nbr_cnv_parameters(factor, offset, code, cnv_control)
     double *factor;
     double *offset;
     int code;
     NBR_CNV_CONTROL_TYPE *cnv_control;
#endif
{

	char func[]="get_nbr_cnv_parameters";

	NBR_SCALING_TYPE *scale;
	
	int done;

	/* Initialize scaling parameters */
	*factor = BADDOUBLE;
	*offset = BADDOUBLE;

	/* Get the conversion entry and scaling parameters corresponding to code */
	scale = cnv_control->scaling_list;
	done = 0;
	while((scale->name != NULL) && !done){
		if(code == scale->code){
				done = 1;
				*factor = scale->factor;
				*offset = scale->offset;
		}
		scale++;
	}

	/* Special conversions */
	switch(code){
	case CNV_BT_VELOCITY_CODE:
	case CNV_VELOCITY_CODE:
		*factor = velocity_scale(*(cnv_control->adcp_config));
		*factor *= (cnv_control->sound_speed /DEFAULT_SOUND_SPEED);
		*offset = 0.0;
		break;
	case CNV_SPECTRAL_WIDTH_CODE:
		*factor = spectral_width_scale(*(cnv_control->adcp_config));
		*factor *= (cnv_control->sound_speed /DEFAULT_SOUND_SPEED);
		*offset = 0.0;
		break;
	case CNV_ECHO_INTENSITY_CODE:
		*factor = 127.3/(cnv_control->echo_scale_temp + 273.0);
		*offset = 0.0;
		break;
	case CNV_NBR_HEADING_CODE:
		*factor = 360.0/65536.0;
		*offset = - (cnv_control->tr_mis->heading);
		break;
	case CNV_NBR_PITCH_CODE:
		*factor = 360.0/65536.0 * pitch_sign(*(cnv_control->adcp_config));
		*offset = -(cnv_control->tr_mis->pitch);
		break;
	case CNV_NBR_ROLL_CODE:
		*factor = 360.0/65536.0 * roll_sign(*(cnv_control->adcp_config));
		*offset = -(cnv_control->tr_mis->roll);
		break;		 
	default:
		/* Nothing to do */
		break;
	}

	/* Check that scaling parameters have been found */
	if((*factor < ADJ_BADFLOAT) && (*offset < ADJ_BADFLOAT)
     && (abs_val(*factor) > 0.0))
		return(0);

	warning_msg(func, "No scaling parameters defined for this");
	warning_msg(NULL, "conversion code");
	*factor = 0.0;
	*offset = 0.0;
	
	return(1);

}

/* ------------------------------------------------------------------
	 This function converts all bins of one NB ADCP raw data into their physical
	 format. 

   RETURNS: the number of converted bins or 0 on error;
	 ------------------------------------------------------------------ */
#if PROTOTYPE_ALLOWED
int nbr_cnv_one_data(/* Pointer to unistat list containing the whole bunch of
                        ADCP bin data */
                     UNISTAT_LIST_TYPE *uni, 
                     /* Code specifying the variable to convert */
                     int cnv_code, 
										 /* List of conversion control parameters */
                     NBR_CNV_CONTROL_TYPE *cnv_control)
#else
     int nbr_cnv_one_data(uni, cnv_code, cnv_control)
     UNISTAT_LIST_TYPE *uni; 
     int cnv_code;
     NBR_CNV_CONTROL_TYPE *cnv_control;
#endif
{

	char func[]="nbr_cnv_one_data";

	USHORT us;
	SHORT  s;
	ULONG  ul;
	UBYTE  ub;
	BYTE   b;
	double d;
	
	double data, factor, offset;
	NBR_CONVERSION_TYPE *cnv;
	int done, i;
		
	/* Get the pointer to the correct conversion list cell */
	done = 0;
	cnv = cnv_control->conversion_list;
	while((cnv->name != NULL) && !done){
		if(cnv->name_code == cnv_code)
			done = 1;
		/* Update pointer */
		cnv++;
	}
	cnv--;
		
	/* Check that a conversion cell has been found */
	if(!done){
		error_msg(func, "No conversion cell defined for this conversion code");
		return(0);
	}
	
	/* Get the scaling parameters for this unistat variable */
	if( get_nbr_cnv_parameters(&factor, &offset, cnv_code, cnv_control) ){
		error_msg(func, "NBR data could not convert data");
		return(0);
	}

	/* Convert all data defined from the conversion list and place it into the
		 unistat variable */
	for(i=0; i<(uni->size); i++){

		/* Fetch the data */
		switch(cnv->type_code){
		case USHORT_VALUE_CODE:
			us = *( (USHORT *) (cnv->data) + i);
			data = (us == BADUSHORT) ? BADDOUBLE : (double) us;
			break;
		case SHORT_VALUE_CODE:
			s = *( (SHORT *) (cnv->data) + i);
			data = (s == BADSHORT) ? BADDOUBLE : (double) s;
			break;
		case UBYTE_VALUE_CODE:
			ub = *( (UBYTE *) (cnv->data) + i);
			data = (ub == BADUBYTE) ? BADDOUBLE : (double) ub;
			break;
		case ULONG_VALUE_CODE:
			ul = *( (ULONG *) (cnv->data) + i);
			data = (ul == BADULONG) ? BADDOUBLE : (double) ul;
			break;
		case BYTE_VALUE_CODE:
			b = *( (BYTE *) (cnv->data) + i);
			data = (b == BADBYTE) ? BADDOUBLE : (double) b;
			break;
		case DOUBLE_VALUE_CODE:
			d = *( (double *) (cnv->data) + i);
			data = (d < ADJ_BADFLOAT) ? (double) d : BADDOUBLE;
			break;
		default:
			error_msg(func, "No type conversion defined for this code");
			return(0);
			break;
		}
	
		/* Convert the data */
		if(data < ADJ_BADFLOAT)
			*(uni->data + i) = data*factor + offset;
		else
			*(uni->data + i) = BADDOUBLE;
		
	}
		
	return(i);
	
}

/* ------------------------------------------------------------------
	 This function converts all bins of all NB ADCP raw data into their physical
	 format.

   RETURNS: CONVERT_ERROR on error, 0 if success.
	 ------------------------------------------------------------------ */
#if PROTOTYPE_ALLOWED
int nbr_cnv_all_data(/* Pointer to unistat list containing all ADCP bindata to 
                        convert */
                     UNISTAT_LIST_TYPE *uni_list,
                     /* Pointer to the complete ADCP data records */
                     char *nbr_buff,
                     /* Pointer to the current conversion control parameters
                      */ 
										 NBR_CNV_CONTROL_TYPE *cnv_control)
#else
     int nbr_cnv_all_data(uni_list, nbr_buff, cnv_control)
     UNISTAT_LIST_TYPE *uni_list;
     char *nbr_buff;
     NBR_CNV_CONTROL_TYPE *cnv_control;
#endif
{

	char func[]="nbr_cnv_all_data";


	UNISTAT_LIST_TYPE *uni;
	DATA_TO_CONVERSION_TYPE *dc;
	int done, cnv_code;

	/* Make a copy of the data into the conversion control buffer */
	memcpy((void *) (cnv_control->buff), (void *) nbr_buff, 
				 cnv_control->buff_size);
	
	/* Convert each data and fill the data array of the unistat list */
	uni = uni_list;
	while(uni->name != NULL){
		/* Get the corresponding conversion code */
		done = 0;
		cnv_code = -BADINT;
		dc = cnv_control->data_to_cnv_list;
		while((dc->name != NULL) && !done){
			if(dc->var_code == uni->code){
				cnv_code = dc->cnv_code;
				done = 1;
			}
			/* Update pointer */
			dc++;
		}
		/* Check that a code has been found and that it is defined */
		if(cnv_code == BADINT){
			warning_msg(func, "No conversion code found for this unistat variable");
			return(CONVERT_ERROR);
		}
		else if(cnv_code > -BADINT){
			/* Convert all data for this unistat variable */
			if( nbr_cnv_one_data(uni, cnv_code, cnv_control) != uni->size ){
				error_msg(func, "While converting NBR data");
				return(CONVERT_ERROR);
			}
		}
		/* Update pointer */
		uni++;
		
	}
	
	return(0);
	
}
