/****************************************************************************/
/* Copyright 1992 MBARI                                                     */
/****************************************************************************/
/* $Header: /home/cvs/oasis3/src/operations/src/spec.c,v 1.1 2003/08/20 19:39:44 headley Exp $			    */
/* Summary  : Spectroradiometer decode routines for decode.c, extract.c	    */
/* Filename : spec.c							    */
/* Author   : Robert Herlien (rah)					    */
/* Project  : OASIS Mooring						    */
/* $Revision: 1.1 $							    */
/* Created  : 05/12/92							    */
/*									    */
/* MBARI provides this documentation and code "as is", with no warranty,    */
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium  */
/* Research Institute to assist in its use, correction, modification, or    */
/* enhancement. This information should not be published or distributed to  */
/* third parties without specific written permission from MBARI.            */
/*									    */
/****************************************************************************/
/* Modification History:						    */
/* 12may92 rah - created						    */
/* $Log: spec.c,v $
/* Revision 1.1  2003/08/20 19:39:44  headley
/* no message
/*
 * Revision 3.1  2001/06/19  13:17:38  13:17:38  oasisa (Oasis users)
 * Periodic Update 6/19/2001 (klh)
 * 
 * Revision 3.0  99/05/12  10:11:27  10:11:27  bobh (Bob Herlien)
 * Added tstring, misc changes
 * 
 * Revision 2.9  98/08/24  13:45:55  13:45:55  bobh (Bob Herlien)
 * Archiving sources after M2/M3 & Eqpac deployments of 1998
 * 
 * Revision 2.8  98/03/17  11:11:42  11:11:42  bobh (Bob Herlien)
 * Archiving sources prior to porting to DOS/Windows
 * 
 * Revision 2.7  97/09/09  09:52:47  09:52:47  bobh (Bob Herlien)
 * Archiving various changes
 * 
 * Revision 2.6  96/05/30  15:07:57  15:07:57  bobh (Bob Herlien)
 * Update for version in use during 1995-6 deployment
 * 
 * Revision 2.2  94/01/17  11:09:59  11:09:59  hebo (Bob Herlien)
 * Misc changes
 * 
 * Revision 2.1  92/09/02  15:43:25  15:43:25  hebo (Bob Herlien)
 * Don't subtract offset from standard deviation
 * 
 * Revision 2.0  92/08/31  15:35:57  15:35:57  hebo (Bob Herlien)
 * August 1992 Deployment.  Changed to allow multiple sensors of same type.
 * 
 * Revision 1.0  92/05/12  18:18:20  18:18:20  hebo (Bob Herlien)
 * Initial revision
 * 
*/
/****************************************************************************/

#include <stdio.h>			/* Standard I/O			    */
#include <mbariTypes.h>			/* MBARI type definitions	    */
#include <mbariConst.h>			/* MBARI constants		    */
#include <decode.h>			/* OASIS controller definitions	    */
#include <time.h>			/* Time				    */
#include <memory.h>			/* for memcpy()			    */
#include <ctype.h>			/* for toupper()		    */
#include <string.h>			/* for strcmp			    */
#include <math.h>

#define PRR_FORMAT	1		/* Format word for PRR spectro	    */
#define SRE		9.539e-06	/* Gain constant for PRR spectro    */
#define MISSING_CHAN_VALUE -999.	/* Placeholder for missing channel  */


/********************************/
/*	External Functions	*/
/********************************/

Extern Nat16	getIntelword( Byte *p ); /* Get word in Intel format	    */
Extern Nat32	getIntellong( Byte *p ); /* Get long word in Intel format   */


/********************************/
/*	Module Local Data	*/
/********************************/

MLocal double		prr_gains[] = { 256.0, 16.0, 1.0, 0.0 };
MLocal char		tmp_buff[256];


/************************************************************************/
/* Function    : init_spec_cal						*/
/* Purpose     : Initialize Spectroradiometer calibration		*/
/* Inputs      : None							*/
/* Outputs     : none							*/
/************************************************************************/
	Void
init_spec_cal( SpecCal *scp )
{
    Reg Int32	bank, chan;

    memset( (void *)scp, sizeof(SpecCal), 0 );

    for ( bank = 0; bank < SPEC_BANKS; bank++ )
	for ( chan = 0; chan < SPEC_CHANS; chan++ )
	{
	    scp->spc_cal[bank][chan].a = 0.0;
	    scp->spc_cal[bank][chan].b = 1.0;
	    scp->spc_cal[bank][chan].c = 0.0;
	    strncpy( scp->spc_cal[bank][chan].name, 
		     "No calibration", CHAN_NAMELEN );
	    strncpy( scp->spc_cal[bank][chan].units, "volts", UNIT_NAMELEN );
	}

} /* init_spec_cal() */


/**************************************************************************/
/* Function    : read_spec_file						  */
/* Purpose     : Reentrant routine to read one file of spectro calibrations*/
/* Inputs      : Name of file, Ptr to cal struct, Boolean for got channel nos*/
/* Outputs     : OK or ERROR						  */
/* Comments    : Discards lines until it finds record with number of chans*/
/*		 then assumes channel calibration records follow	  */
/*		 Assumes bank number is zero-relative (so leaves it alone)*/
/*		 but that channel number is one-relative, so subtracts 1  */
/**************************************************************************/
	Status
read_spec_file( char *fileName, SpecCal *scp, MBool gotChans )
{
    FILE	*fp;
    Int		bank, chan, cmin, cmax;
    Reg char	*p;
    SpecChanCal	cal;

    if ( (fp = fopen(fileName, "rb")) == (FILE *)NULL )
	return( ERROR );

    while( fgets(tmp_buff, sizeof(tmp_buff), fp) != NULL )
    {
	if ( (p = strchr(tmp_buff, '\n')) != NULL )
	    *p = '\0';
	if ( (tmp_buff[0] == '#') || (strlen(tmp_buff) == 0) )
	    ;				/* Skip comments & blank lines	*/
	else if ( !gotChans )
	{
	    scp->spc_nbanks = sscanf(tmp_buff, " %d %d %d %d %d %d",
				     &scp->spc_nchans[0], &scp->spc_nchans[1],
				     &scp->spc_nchans[2], &scp->spc_nchans[3],
				     &scp->spc_nchans[4], &scp->spc_nchans[5]);
	    if( scp->spc_nbanks > 0 )
		gotChans = TRUE;
	}
	else if ( sscanf(tmp_buff, " %d %d %d %8s %lg %lg %lg %d %d %20s", 
		    &bank, &chan, &cal.type, &cal.name, &cal.a, &cal.b,
		    &cal.c, &cmin, &cmax, &cal.units) >= 7 )
	    memcpy( (void *)&scp->spc_cal[bank][chan-1], 
		    (void *)&cal, sizeof(cal) );
	else
	    read_spec_file( tmp_buff, scp, gotChans );
    }

    fclose( fp );
    return( OK );

} /* read_spec_file() */


/**************************************************************************/
/* Function    : read_spec_cal						  */
/* Purpose     : Read Spectroradiometer calibration file		  */
/* Inputs      : Name of file, Ptr to calibration structure to fill in	  */
/* Outputs     : OK or ERROR						  */
/**************************************************************************/
	Status
read_spec_cal( char *fileName, SpecCal *scp )
{
    Reg Int	i;

    init_spec_cal( scp );
    scp->spc_totchans = 0;

    if ( read_spec_file(fileName, scp, FALSE) != OK )
	return( ERROR );

    for( i = 0; i < SPEC_BANKS; i++ )
    {
	if ( scp->spc_nchans[i] > SPEC_CHANS )
	    return( ERROR );
	scp->spc_totchans += scp->spc_nchans[i];
    }

    if ( scp->spc_totchans <= 0 )
	return( ERROR );

    return( OK );

} /* read_spec_cal() */


/************************************************************************/
/* Function    : decode_value						*/
/* Purpose     : Decode one spectro value				*/
/* Inputs      : Pointer to Spectro data, channel calibration struct, Bool*/
/* Outputs     : Value as double precision number			*/
/* Comment     : Used only by decode_spectro(), currently unused	*/
/*		 a coefficient not used -- MER always uses linear calibr*/
/************************************************************************/
	double
decode_value( Byte *p, SpecChanCal *sccp, MBool apply_offset )
{
    Nat32	raw;
    Int32	mantissa;
    Int32	exponent;
    double	voltage;

    raw = getIntellong( p );			/* Get hex value	*/
    mantissa = raw & 0x7fffff;			/* Get mantissa		*/
    if ( raw & 0x800000 )			/* Get mantissa's sign	*/
	mantissa = -mantissa;
    exponent = ((raw >> 24) & 0xff) - 23;	/* Get exponent		*/

    voltage = (double)mantissa * pow(2.0, (double)exponent);

    if ( apply_offset )
	voltage -= sccp->c;

    if ( sccp->type == 6 )
	return( sccp->a * voltage / sccp->b );

    return( voltage / sccp->b );

} /* decode_value() */


/************************************************************************/
/* Function    : decode_spectro						*/
/* Purpose     : Decode Spectroradiometer information			*/
/* Inputs      : Pointer to Spectro data, length, ptr to return struct	*/
/* Outputs     : OK, SIZE_ERR, FMT_ERR, or CHKSUM_ERR			*/
/* Comments    : MER Spectro decode routine, currently unused		*/
/************************************************************************/
	Status
decode_spectro( Byte *spdata, Int splen, SpecDecode *sdp, SpecCal *scp )
{
    Nat32	val;
    Int32	i, j, n;
    Byte	chksum;
    SpecChanCal	*sccp;
    SpecChanDecode *scdp;

    memset( (void *)sdp, sizeof(SpecDecode), 0 );

    if ( splen < (Int)((4 * scp->spc_totchans * sizeof(Nat32)) + 9) )
	return( SIZE_ERR );			/* Check size, rtn if bad   */

    if ( *spdata != 'S' )			/* Look for leading 'S',    */
	return( FMT_ERR );			/*   return if bad	    */

    if ( getIntelword(spdata+1) != splen )	/* Look for length field,   */
	return( FMT_ERR );			/*   return if bad	    */

    chksum = 0;					/* Init checksum	    */
    for ( i = 0; i < splen - 1; i++ )		/* Compute checksum	    */
	chksum += spdata[i];

    if ( chksum != spdata[splen-1] )		/* Return error if bad chksum*/
	return( CHKSUM_ERR );		

    val = getIntellong( spdata + 3 );		/* Get time/date field	    */

    sdp->spc_year = ((val >> 9) & 0x3f) + 1980;	/* Decode year		    */
    sdp->spc_day = (val & 0xff);		/* Decode julian day	    */
    if ( val & 0x8000 )
	sdp->spc_day += 0x100;
    sdp->spc_sec = (val >> 16) & 0xffff;	/* Decode seconds since 00:00*/
    if ( val & 0x100 )
	sdp->spc_sec += 0x10000L;

    for ( i = 0, n = 8; i < SPEC_BANKS; i++ )
	for ( j = 0; j < scp->spc_nchans[i]; j++ )
	{
	    sccp = &scp->spc_cal[i][j];
	    scdp = &sdp->spc_chan[i][j];
	    scdp->spc_mean = decode_value( spdata + n, sccp, TRUE );
	    n += sizeof(Nat32);
	    scdp->spc_std = decode_value( spdata + n, sccp, FALSE );
	    n += sizeof(Nat32);
	    scdp->spc_min = decode_value( spdata + n, sccp, TRUE );
	    n += sizeof(Nat32);
	    scdp->spc_max = decode_value( spdata + n, sccp, TRUE );
	    n += sizeof(Nat32);
	}

    return( OK );

} /* decode_spectro() */


/************************************************************************/
/* Function    : decode_prr_chan					*/
/* Purpose     : Decode one channel of PRR-600 Spectroradiometer data	*/
/* Inputs      : Raw data, ptr to channel calibration			*/
/* Outputs     : Channel value						*/
/************************************************************************/
	double
decode_prr_chan( Int16 ival, SpecChanCal *sccp )
{
    double	volts;
    Nat16	gainbits;

    gainbits = (ival & 0xc000) >> 14;

    if ( ival & 0x2000 )			/* Bit 12 is sign bit	*/
	ival |= 0xc000;
    else
	ival &= ~0xc000;

    if ( gainbits >= 3 )
	volts = 1e99;
    else
	volts = prr_gains[gainbits] * (double)ival * SRE;

    if ( sccp == (SpecChanCal *)NULL )
	return( volts );

    switch ( sccp->type )
    {
      case 1:
      case 2:
      case 3:
      case 4:
	return( (volts - sccp->c) / sccp->b );

      case 5:
	return( sccp->c + (sccp->b * volts) + (sccp->a * volts * volts) );

      case 6:
	return( (sccp->a * (volts - sccp->c)) / sccp->b);

      case 7:
	return( sccp->a * sccp->b * (volts - sccp->c) );

      case 0:
	return( 0.0 );

      default:
	return( MISSING_CHAN_VALUE );
    }

} /* decode_prr_chan() */


/************************************************************************/
/* Function    : decode_prr						*/
/* Purpose     : Decode PRR-600 Spectroradiometer data			*/
/* Inputs      : Pointer to Spectro data, length, ptr to return struct	*/
/* Outputs     : OK, SIZE_ERR, or FMT_ERR				*/
/************************************************************************/
	Status
decode_prr( Byte *spdata, Int splen, PrrDecode *sdp, SpecCal *scp )
{
    Nat32	bank, chan, chansForThisBank;
    Int32	bytesUsed;
    Int16	ival;

    sdp->prr_nbanks = 0;

    for ( bank = 0; bank < SPEC_BANKS; bank++ )
    {
	sdp->prr_bank[bank].prr_nchans = 0;
	for ( chan = 0; chan < SPEC_CHANS; chan++ )
	    sdp->prr_bank[bank].prr_chan[chan] = MISSING_CHAN_VALUE;
    }

    if ( getIntelword(spdata) != PRR_FORMAT )
	return( FMT_ERR );

    bytesUsed = sizeof( Nat16 );

    for ( bank = 0; (bank < SPEC_BANKS) && (bytesUsed < splen); bank++ )
    {
	sdp->prr_nbanks = bank + 1;
	chansForThisBank = getIntelword( spdata + bytesUsed );
	bytesUsed += sizeof( Nat16 );

	for ( chan = 0; (chan < chansForThisBank) && (bytesUsed < splen);
	      chan++, bytesUsed += sizeof(Int16) )
	{
	    sdp->prr_bank[bank].prr_nchans = chan + 1;
	    ival = (Int16)getIntelword( spdata + bytesUsed );

	    if ( scp == (SpecCal *)NULL )
		sdp->prr_bank[bank].prr_chan[chan] =
		    decode_prr_chan( ival, (SpecChanCal *)NULL );
	    else
		sdp->prr_bank[bank].prr_chan[chan] =
		    decode_prr_chan( ival, &scp->spc_cal[bank][chan] );
	}
    }

    return( OK );

} /* decode_prr() */


/************************************************************************/
/* Function    : read_satlantic_cal					*/
/* Purpose     : Read Satlantic Spectro calibration file		*/
/* Inputs      : Name of file, Ptr to calibration structure to fill in	*/
/* Outputs     : OK or ERROR						*/
/* Comments    : Discards lines until it finds record with number of chans*/
/*		 then assumes channel calibration records follow	*/
/************************************************************************/
	Status
read_satlantic_cal( char *name, SatlanticCal *scp )
{
    FILE	*fp;
    MBool	got_chans;
    Int		chan;

    memset( (void *)scp, sizeof(SatlanticCal), 0 );

    for ( chan = 0; chan < MAX_SAT_CHANS; chan++ )
    {
	scp->sat_cal[chan].sat_factor = 1.0;
	scp->sat_cal[chan].sat_immers = 1.0;
	scp->sat_cal[chan].sat_offset = 0.0;
	strncpy( scp->sat_cal[chan].sat_name, "No calibration", CHAN_NAMELEN );
    }

    got_chans = FALSE;
    chan = 0;

    if ( (fp = fopen(name, "rb")) == (FILE *)NULL )
	return( ERROR );

    while( fgets(tmp_buff, sizeof(tmp_buff), fp) != NULL )
    {
	if ( tmp_buff[0] == '#' )
	    ;				/* Skip comments		*/
	else if ( !got_chans )
	{
	    if( sscanf(tmp_buff, " %d %d", &scp->sat_unused_chans,
		       &scp->sat_chans) > 1 )
		got_chans = TRUE;
	}
	else if ( sscanf(tmp_buff, " %6s %lg %lg %lg",
			 &scp->sat_cal[chan].sat_name,
			 &scp->sat_cal[chan].sat_factor,
			 &scp->sat_cal[chan].sat_immers, 
			 &scp->sat_cal[chan].sat_offset) >= 4 )
	{
	    chan++;
	}
    }

    if ( scp->sat_chans <= 0 )
	return( ERROR );

    fclose( fp );
    
    return( OK );

} /* read_satlantic_cal() */


/************************************************************************/
/* Function    : decode_satlantic					*/
/* Purpose     : Decode Satlantic Spectro data				*/
/* Inputs      : Pointer to Spectro data, length, ptr to return struct	*/
/* Outputs     : OK or SIZE_ERR						*/
/************************************************************************/
	Status
decode_satlantic( Byte *satdata, Int len,
		  SatlanticDecode sdp, SatlanticCal *scp )
{
    Int			chan;
    double		raw;
    Byte		*datp;
    SatlantChanCal	*sccp;

    if ( len < (Int)((scp->sat_unused_chans + scp->sat_chans) * sizeof(Nat16)) )
	return( SIZE_ERR );

    datp = satdata + (scp->sat_unused_chans * sizeof(Nat16));

    for ( chan = 0; chan < scp->sat_chans; chan++, datp += sizeof(Nat16) )
    {
	sccp = &scp->sat_cal[chan];
	raw = (double)getIntelword(datp) - sccp->sat_offset;
/*
	if ( raw < 0.0 )
	    raw = 0.0;
*/
	sdp[chan] = raw * sccp->sat_factor * sccp->sat_immers;
    }

    return( OK );

} /* decode_satlantic */
