/*
   FILE: readtnbp.c

         Routines for READing Transect NarrowBand Processed data files.

*/
#include <dbinc.h>  /* includes geninc.h */
#include <ioserv.h>
#include <mc0.h>
/* #include "rawbb.h" */
#include "tran_nbp.h"
/** 1999/07/06 Pierre Jaccard
    The next include is required for correct function declaration. **/
#include "tran_dcl.h" 

#define TRUE 1
#define ID_NOT_FOUND 99
#define N_DIRECT_CMD 9  /* ?? specific to Teri's data -- need to reduce direct_cmd_size
                              by this much because of loss of CR character at EOL */
#if PROTOTYPE_ALLOWED
void set_depth(DB_ENSEMBLE_TYPE *db_ens, NBP_ENSEMBLE_TYPE *ens);
#else
void set_depth();
#endif

extern char msg[];

#if PROTOTYPE_ALLOWED
int read_header(FILE *fp, NBP_HEADER_TYPE *header, long *ens_start)
#else
int read_header(fp, header, ens_start)
   FILE *fp;
   NBP_HEADER_TYPE *header;
   long *ens_start;
#endif
{
   int n, c, d;
   static char buff[NBP_HEADER_FIXED_SIZE], *buf_ptr;

   /* use while loop to search for next good header so one can resynch/
      recover data after encountering an error in the input file */
   while ( (c = getc(fp)) != EOF && (c != 0x7f  ||
           (d = getc(fp)) != EOF && d != 0x7e ))
   {
      if (c != EOF && d == 0x7f)
         ungetc(d, fp);
   }
   if (c == EOF || d == EOF)
      return(EOF);
   *ens_start = ftell(fp) - 2;

   if (fread(buff, NBP_HEADER_FIXED_SIZE, 1, fp) != 1)
      return(EOF);

   buf_ptr = &buff[0];
   buf_ptr += convert_array_short((char *)&header->n_bytes_in_data_segment, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 1);
   buf_ptr += copy_byte((char *)&header->n_depth_cells, buf_ptr, 2, AS_IS, NULL);

   if ( (header->offset = (USHORT *) calloc(header->n_data_types, sizeof(USHORT))) == NULL )
      return(INSUFFICIENT_MEMORY);
   if ( error_found( fread(header->offset, sizeof(USHORT), header->n_data_types, fp) != header->n_data_types,
      "Can't read header offsets."))
      return(READ_ERROR);
   convert_array_short((char *) header->offset, (char *) header->offset, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, header->n_data_types);

   /* Transect bug: for the configuration data type only, the
   offset is written as MSB LSB, and the LSB is too small by 1.
   Nils Oien corrected the unswapping for any platform. 
   */
   if (header->n_data_types == 1)
   {
      copy_byte((char *) header->offset, (char *) header->offset, 
                                             2, INVERTED, NULL);
      (header->offset[0])++; 
   }

   return(OK);
}

#if PROTOTYPE_ALLOWED
int read_configuration(FILE *fp, NBP_ENSEMBLE_TYPE *ens)
#else
int read_configuration(fp, ens)
   FILE *fp;
   NBP_ENSEMBLE_TYPE *ens;
#endif
{
   NBP_CONFIGURATION_TYPE *conf;
   static char buff[NBP_CONFIGURATION_FIXED_SIZE], *buf_ptr;

   conf = &ens->conf;
   if (error_found( fread(buff, NBP_CONFIGURATION_FIXED_SIZE, 1, fp) != 1, "Can't read configuration."))
      return(READ_ERROR);

   buf_ptr = buff;

   buf_ptr += copy_byte((char *)&conf->adcp_type, buf_ptr, 3, AS_IS, NULL);
   buf_ptr += convert_array_short((char *) &conf->ADCP_serial_number, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 2);
   buf_ptr += copy_byte((char *)&conf->n_beams, buf_ptr, 2, AS_IS, NULL);
   buf_ptr += convert_array_short((char *) &conf->frequency, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 1);
   buf_ptr += copy_byte((char *)&conf->range_switch, buf_ptr, 5, AS_IS, NULL);
   buf_ptr += convert_array_long((char *) &conf->ping_interval, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 1);
   buf_ptr += convert_array_short((char *) &conf->pings_per_ens, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 1);
   buf_ptr += copy_byte((char *)&conf->bin_length, buf_ptr, 1, AS_IS, NULL);
   buf_ptr += convert_array_short((char *) &conf->pulse_length, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 2);
   buf_ptr += convert_array_long((char *) &conf->adcp_depth, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 1);
   buf_ptr += copy_byte((char *)&conf->avg_method, buf_ptr, 1, AS_IS, NULL);
   buf_ptr += convert_array_long((char *) &conf->avg_interval, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 4);
   buf_ptr += convert_array_short((char *) &conf->intensity_scale, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 3);
   buf_ptr += convert_array_long((char *) &conf->sound_speed, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 1);
   buf_ptr += copy_byte((char *)&conf->use_default_sound_speed, buf_ptr, 2, AS_IS, NULL);
   buf_ptr += convert_array_long((char *) &conf->start_discharge, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 4);
   buf_ptr += copy_byte((char *)&conf->top_q_method, buf_ptr, 2, AS_IS, NULL);
   buf_ptr += convert_array_short((char *) &conf->power_curve_exp, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 1);
   buf_ptr += convert_array_long((char *) &conf->spare1, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 2);
   buf_ptr += copy_byte((char *)&conf->dply_name_size, buf_ptr, 1, AS_IS, NULL);

   if ( (conf->name = calloc(conf->dply_name_size + 1, sizeof(char))) == NULL )
      return(INSUFFICIENT_MEMORY);
   if (error_found( fread(conf->name, conf->dply_name_size, 1, fp) != 1, "Can't read configuration deployment name."))
      return(READ_ERROR);

   if (error_found( fread(&conf->primary_drive, sizeof(BYTE), 3, fp) != 3, "Can't read configuration drives, etc."))
      return(READ_ERROR);

   /* ?? This adjustment is specific to Teri's data! direct commands lost CRs */
   /* conf->direct_cmd_size -= N_DIRECT_CMD; */

   if ( (conf->direct_cmd = calloc(conf->direct_cmd_size + 1, sizeof(char))) == NULL )
      return(INSUFFICIENT_MEMORY);
   if (error_found( fread(conf->direct_cmd, conf->direct_cmd_size, 1, fp) != 1, "Can't read configuration direct command set."))
      return(READ_ERROR);

   return (OK);
}

#if PROTOTYPE_ALLOWED
int read_leader(FILE *fp, NBP_ENSEMBLE_TYPE *ens)
#else
int read_leader(fp, ens)
   FILE *fp;
   NBP_ENSEMBLE_TYPE *ens;
#endif
{

   NBP_LEADER_TYPE *leader;
   static char buff[NBP_LEADER_SIZE], *buf_ptr;

   leader = &ens->leader;
   if (error_found( fread(buff, NBP_LEADER_SIZE, 1, fp) != 1, "Can't read leader."))
      return(READ_ERROR);

   buf_ptr = buff;

   buf_ptr += convert_array_long((char *) &leader->ens_start_seconds, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 1);
   buf_ptr += convert_array_short((char *) &leader->ens_number, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 13);
   buf_ptr += convert_array_long((char *) &leader->x_displacement, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 7);
   buf_ptr += copy_byte((char *)&leader->bit_result, buf_ptr, 1, AS_IS, NULL);
   /* ?? leader seems to have an extra byte of padding! */
   buf_ptr++;
   buf_ptr += convert_array_short((char *) &leader->n_raw_ens, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 1);

   ens->leader_present = 1;
   return (OK);
}

#if PROTOTYPE_ALLOWED
int read_velocity(FILE *fp, NBP_ENSEMBLE_TYPE *ens)
#else
int read_velocity(fp, ens)
   FILE *fp;
   NBP_ENSEMBLE_TYPE *ens;
#endif
{
   SHORT *u, *v, *w, *e;
   int n_vel, ibuff, i;
   static USHORT buff[4 * MAX_BINS];

   u = ens->u;
   v = ens->v;
   w = ens->w;
   e = ens->e;

   n_vel = 4 * ens->header.n_depth_cells;
   if (error_found( fread(buff, sizeof(USHORT), n_vel, fp) != n_vel, "Can't read velocity."))
      return(READ_ERROR);
   convert_array_short((char *)buff, (char *)buff, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, n_vel);

   for (i = 0, ibuff = 0; i < ens->header.n_depth_cells; i++, ibuff += 4)
   {
      u[i] = (buff[ibuff    ] == 0x8000 ? BADSHORT : (SHORT) buff[ibuff    ]);
      v[i] = (buff[ibuff + 1] == 0x8000 ? BADSHORT : (SHORT) buff[ibuff + 1]);
      w[i] = (buff[ibuff + 2] == 0x8000 ? BADSHORT : (SHORT) buff[ibuff + 2]);
      e[i] = (buff[ibuff + 3] == 0x8000 ? BADSHORT : (SHORT) buff[ibuff + 3]);
   }

   ens->vel_present = TRUE;
   return (OK);
}

#if PROTOTYPE_ALLOWED
int read_amp(FILE *fp, NBP_ENSEMBLE_TYPE *ens)
#else
int read_amp(fp, ens)
   FILE *fp;
   NBP_ENSEMBLE_TYPE *ens;
#endif
{
   int n_amp, i, j;

   n_amp = 4 * ens->header.n_depth_cells;
   if (error_found( fread(ens->raw_amp, sizeof(UBYTE), n_amp, fp) != n_amp,
      "Can't read echo intensity."))
      return(READ_ERROR);

   for (i = 0; i < ens->header.n_depth_cells; i++)
   {
      j = i * 4;
      ens->mean_amp[i] = (UBYTE) 
         round((ens->raw_amp[j] + ens->raw_amp[j+1] + ens->raw_amp[j+2] + ens->raw_amp[j+3]) / 4);
   }

   ens->amp_present = TRUE;
   return (OK);
}

#if PROTOTYPE_ALLOWED
int read_pgood(FILE *fp, NBP_ENSEMBLE_TYPE *ens)
#else
int read_pgood(fp, ens)
   FILE *fp;
   NBP_ENSEMBLE_TYPE *ens;
#endif
{
   if (error_found( fread(ens->pg, sizeof(BYTE), ens->header.n_depth_cells, fp) != ens->header.n_depth_cells,
      "Can't read percent-good."))
      return(READ_ERROR);

   ens->pg_present = TRUE;
   return (OK);
}

#if PROTOTYPE_ALLOWED
int read_discharge(FILE *fp, NBP_ENSEMBLE_TYPE *ens)
#else
int read_discharge(fp, ens)
   FILE *fp;
   NBP_ENSEMBLE_TYPE *ens;
#endif
{
   LONG *discharge;

   discharge = ens->discharge;

   if (error_found(fread(discharge, sizeof(LONG), ens->header.n_depth_cells, fp) != ens->header.n_depth_cells, "Can't read discharge."))
      return(READ_ERROR);
   convert_array_long((char *) discharge, (char *) discharge, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, ens->header.n_depth_cells); 

   ens->discharge_present = TRUE;
   return(OK);
}

#if PROTOTYPE_ALLOWED
int read_navigation(FILE *fp, NBP_ENSEMBLE_TYPE *ens)
#else
int read_navigation(fp, ens)
   FILE *fp;
   NBP_ENSEMBLE_TYPE *ens;
#endif
{
   int n;
   static char buff[TRANSECT_NAV_SIZE], *buf_ptr;
   TRANSECT_NAV_TYPE *nav;

   nav = &ens->nav;
   if (error_found( fread(buff, TRANSECT_NAV_SIZE, 1, fp) != 1, "Can't read navigation."))
      return(READ_ERROR);

   buf_ptr = buff;
   buf_ptr += convert_array_short((char *) &nav->u, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 2);
   buf_ptr += convert_array_long ((char *) &nav->x, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 5);
   buf_ptr += convert_array_short((char *) &nav->filter_width, buf_ptr, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 1);

   ens->nav_present = TRUE;
   return (OK);
}

typedef struct
{
   int id;
   int data_present;
   int (*read_data) ();
}  DATA_LIST_ELEMENT_TYPE;

DATA_LIST_ELEMENT_TYPE dt_list[] =
{
   {LEADER_ID, 0, read_leader},
   {VELOCITY_ID, 0, read_velocity},
   {AMPLITUDE_ID, 0, read_amp},
   {PGOOD_ID, 0, read_pgood},
   {DISCHARGE_ID, 0, read_discharge},
   {NAVIGATION_ID, 0, read_navigation},
   {CONFIGURATION_ID, 0, read_configuration}
};

/*

   FUNCTION:  read_ensemble

              Reads an entire NBP_ENSEMBLE_TYPE from the input file
              and copies it into a DB_ENSEMBLE_TYPE.  

*/
#if PROTOTYPE_ALLOWED
int read_ensemble(FILE *fp, NBP_ENSEMBLE_TYPE *ens, DB_ENSEMBLE_TYPE *db_ens)
#else
int read_ensemble(fp, ens, db_ens)
   FILE *fp;
   NBP_ENSEMBLE_TYPE *ens;
   DB_ENSEMBLE_TYPE *db_ens;
#endif
{
   int found, i, j, error = OK, num_items = sizeof(dt_list)/sizeof(DATA_LIST_ELEMENT_TYPE);
   USHORT id, checksum;
   long ens_start, save_ofs;
   static CONFIGURATION_1_TYPE prev_conf;
   static char *prev_bc;
   static int prev_bc_nbytes = 0;

   /* conf1.num_bins serves as a flag; passing it in as zero
   and putting that value in prev_conf
   ensures that db_ens->blkvar_has_changed will get set when
   a configuration is read
   */
   if (db_ens->conf1.num_bins == 0)
      prev_conf.num_bins = 0;

   error = read_header(fp, &ens->header, &ens_start);
   if (error != OK)
      goto on_error;

   for (i = 0; i < ens->header.n_data_types; i++)
   {
      save_ofs = ftell(fp);
      if (fseek(fp, ens_start + ens->header.offset[i] - 1, SEEK_SET))
      {
         error = SEEK_ERROR;
         goto on_error;
      }
      if (fread((char *) &id, sizeof(USHORT), 1, fp) != 1)
      {
         error = READ_ERROR;
         goto on_error;     
      }
      convert_array_short((char *)&id, (char *) &id, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 1);
      found = 0;
      for (j = 0; j < num_items && !found; j++)
         found = (id == dt_list[j].id);
      if (found)
      {
         error = (*dt_list[j-1].read_data)(fp, ens);
         if (error != OK)
            goto on_error;
         dt_list[j-1].data_present = TRUE;
      }
      else
      {
         error = ID_NOT_FOUND;
         goto on_error;
      }
   }
   free(ens->header.offset);

   if (fread(&checksum, sizeof(USHORT), 1, fp) != 1)
   {
      error = READ_ERROR;
      goto on_error;
   }
   /* skipping checksum verification for now... 
      there are some inconsistencies between files and documentation,
      in particular:
      1) an extra byte in the leader structure, probably between the
         bit result and n_raw_ensembles fields
      2) the configuration offset reads 0008 when it should be 0900
      I do not know how these affect the checksum.  
      Still waiting for a callback from RDI on this.  95/02/15 */

   convert_array_short((char *)&checksum, (char *) &checksum, PC_COMPATIBLE_HOST, HOST_ENVIRONMENT, 1); 

   if (ens->leader_present == 0) /* must have read just a configuration-type segment */
   {
      fill_config1(&(db_ens->conf1), &(ens->conf));
      db_ens->blkvar_has_changed = cmp_byte((char *)&(db_ens->conf1), (char *)&prev_conf, sizeof(CONFIGURATION_1_TYPE));
      move_byte((char *)&(db_ens->conf1), (char *)&prev_conf, sizeof(CONFIGURATION_1_TYPE));

      if (db_ens->blkvar_has_changed)
         set_depth(db_ens, ens);

      error = set_block_comments(db_ens, ens); /* deploy. name & direct commands */
      if (error) goto on_error;
      db_ens->blkvar_has_changed |= (prev_bc_nbytes != db_ens->bc_nbytes);
      if (!db_ens->blkvar_has_changed) /* compare only if conf hasn't changed and bc size is same */
         db_ens->blkvar_has_changed |= cmp_byte(db_ens->bc, prev_bc, prev_bc_nbytes);
      if (db_ens->bc_nbytes > 0 && prev_bc_nbytes != db_ens->bc_nbytes)
         if (error_found( prev_bc == NULL ? (prev_bc = malloc(db_ens->bc_nbytes)) == NULL : 
                                            (prev_bc = realloc(prev_bc, db_ens->bc_nbytes)) == NULL,
            "Can't allocate memory for block comments.")) 
         {
            error = INSUFFICIENT_MEMORY;
            goto on_error;
         }
      move_byte(db_ens->bc, prev_bc, db_ens->bc_nbytes);
      prev_bc_nbytes = db_ens->bc_nbytes;

      if (ens->conf.dply_name_size > 0)
         free(ens->conf.name);
      if (ens->conf.direct_cmd_size > 0)
         free(ens->conf.direct_cmd);
      return(read_ensemble(fp, ens, db_ens));  /* now try to read a regular data-type segment */
   }
   else
   {
      sprintf(msg, "%6hu", ens->leader.ens_number);
      report_msg(msg);
      return(OK);
   }
   on_error:  /* try to recover from read-related errors, otherwise quit */
      if (error == EOF)
         return(EOF);
      report_msg("   BAD");
      switch(error)
      {
         case SEEK_ERROR:
            if (fseek(fp, save_ofs, SEEK_SET)) break;
         case READ_ERROR:
         case ID_NOT_FOUND:
            return(read_ensemble(fp, ens, db_ens));
         default:
            break;
      }
      report_msg("\n Unrecoverable error in read_ensemble, giving up on this file.\n");
      return(EOF);
}
