/*
@(#) File name: fsdf_header.c  Release: 1.1  Date: 7/28/94, 14:00:08
*/
/****************************************************************************
FILE:
	fsdf_header
	
	FIRE Standard Data Format (SDF) header file scanning routines.

AUTHOR: Meng-chun Lin
	Mail stop 157B, EOSDIS LANGLEY DAAC, Hampton, VA
	(804)864-8657.  e-mail: M.LIN@LaRC.NASA.GOV
	
PURPOSE/DESCRIPTION:
	This file contains a primary function (i.e., SDF_HEADER) used to parse 
	the FIRE SDF defined header file, and two supporting functions (i.e.,
	SDF_DDR, DDR_VAR).  Upon successful invocation, the SDF_HEADER funciton
	will return an array of all data variable's field definitions in the 
	observation DDR and ancillary DDR, if applicable.  These field 
	definitions can be used to read the binary data out of the observation 
	file(s) and ancillary file(s).

	NOTE: FIRE CI1 SRB data has changed the SDF format to accomodate their 
	needs (See header file).  In order for this program to work on both 
	FIRE CI1 SRB and other SDF data sets, a preprocessor option is defined. 

FUNCTIONS:
	sdf_header - is the primary function which returns an array of  
	     variables in the observation file and if applicable, an array of 
	     variables in the ancillary file.
	sdf_ddr - scans through the DDR section.  It extracts the record name, 
	     logical and physical record sizes, and loops through the data 
	     variable descriptors within the DDR.
	DDR_var - extracts short name, type and length, display format,
	     scaling constants, and min. max. values for a data variable.  	

*****************************************************************************/
#ifdef sccs
static char sccsid[] = "File: fsdf_header.c  Release: 1.1  Date: 7/28/94, 14:00:08"
#endif

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/param.h>          /* Constant MAXPATHLEN is defined here. */
#include <string.h>

#include "fire_sdf.h"

extern int  str_trim();

/****************************************************************************
FUNCTION:
	sdf_header ()

PURPOSE/DESCRIPTION:
	Function sdf_header scans the SDF defined header file.  It reads the 
	ancillary data descriptor record (DDR), if any, and observation data 
	description record (DDR) information from the header file.  It returns 
	the two arrays of variables field definitions, one for observation
	the other for ancillary variables (See Notes below for the schematic
	drawings on both record arrays).  It also returns the number of 
	data variables in each of the two segments.

	NOTE:  Since the storage for the variables information is dynamically
	allocated, the calling routine should 'free' the pointer to insure no
	memory leak condition can occur.
	
	The following paragraph pertains to FIRE CI1 SRB:
	Since the prefix area has the same variables throughout the observation
	and the ancillary files in the SRB data set.  Also, it is necessary 
	to know mnemonics in the prefix area for the purpose of checksum, the 
	variables in the prefix area are not stored in the returned arrays.  
	Only the variables in the data area for both ancillary and observation 
	are stored in the arrays. 

INVOCATION:
	return_value = sdf_header (file_name, &obs_dat, &aux_dat,  
	                           swap_flg, nobs);

   WHERE
	<file_name> - I
	   is the file name of the observation data without file extension.
	<obs_dat> - I, O
	   is the address of the pointer points to the base of a list of 
	   data descriptor records.  It returns the information of the number 
           of data variables in DDR (Data Descriptor Record), the logical 
	   record size, block size, and the observation DDR record name.
	<aux_dat> - I, O
	   is the address of the pointer points to the base of a list of 
	   data descriptor records.  It returns the information of the number 
           of data variables in DDR (Data Descriptor Record), the logical 
	   record size, block size, optionally prefix size, and the ancillary 
           DDR record name.  The calling routine should send in a NULL 
           pointer if the data set does not have ancillary files.
	<swap_flg> - I, O
	   returns the value indicating whether or not the bytes need to be 
	   swapped in the data files.  A 0 means no byte-swapping is needed, 
	   A 1 means byte-swapping is needed. 
	<nobs> - I, O
	   returns the number of records defined in the observation segment.
	   This value can be used to determine if there is any observation 
	   variables.
	<return_value> - O
	   returns the number of records defined in the ancillary segment.
	   It returns -1 when there is an error.


FILE/RECORD REFERENCES:	None.

EXTERNAL ROUTINES: None.

NOTES:

The format of the tape header file is described as follows:
1.)  General Segment.  The first 23 logical records (80 bytes per logical 
     record).  This segment ends with the phrase "END OF GENERAL SEGMENT" 
     in the 22nd logical record, then followed by a blank 80-byte record.  
2.)  Ancillary Segment.  It ends with the phrase "END OF ANCILLARY SEGMENT", 
     then followed by a blank 80-byte record.  If there is an ancillary 
     file, it starts with the phrase "RECORD NAME: ".  FIRE data can have 
     no ancillary file or multiple ancillary files.  
3.)  Observation Segment.  It starts with the phrase "RECORD NAME: ", and 
     ends with the phrase "END OF OBSERVATION SEGMENT", and one 80-byte 
     logical record of blanks.
4.)  The array returned to the "obs_dat" and "aux_dat" are as follows:
               ==============================
 header_rec1  |nvars| .. |rec_name|vd_ptr ---|--->----------------------------
               ==============================    | ddr_var1 | ddr_var2 | ...  |  
 header_rec2  |     | .. | ...    | ...      |    ----------------------------
               ==============================   
   ...        | ... | .. | ...    | ...      |
               ==============================
              | ... | .. | ...    | ...   ---|--->----------------------------
               ==============================    | ddr_var1 | ddr_var2 | ...  |
                                                  ----------------------------
          
*****************************************************************************/

int sdf_header (file_name, obs_dat, aux_dat, swap_flg, nobs)
char  *file_name;
header  **obs_dat, **aux_dat;
int  *swap_flg, *nobs;
{
    char  buf[241], str[81], *s;
    FILE  *fptr;
    int  j, nauxs=0; 
    void  sdf_ddr();


    if ((fptr = fopen (file_name, "r")) == NULL)    {
        fprintf (stderr,"ERROR:Can't open FIRE header file:\t%s\n", file_name);
        return (-1);
    }

    *swap_flg = 0;

	/****  FIRE header file is in ASCII format.  It consists of three 
         ****  segments, starting with general segment, followed by ancillary 
         ****  segment, and end with observation segment.
         ****/
    while ((fgets (buf, RECSIZE+1, fptr) != NULL))    {
	if ((strstr (buf, "VAX") != (char *)NULL) || 
            (strstr (buf, "vax") != (char *)NULL) ||
            (strstr (buf, "Vax") != (char *)NULL))
	    *swap_flg = 1;
	if (strstr (buf, END_GENERAL) != (char *)NULL)    break;
    }
        /***  Skip the blank lines.  ***/
    while ((fgets (buf, RECSIZE+1, fptr) != NULL))    {
        if (str_trim (buf, 0) != 0)    break;
    }

    j = 0;
	/**** Ancillary segment is followed after general segment. */
    while (strstr (buf, END_ANCILLARY) == (char *)NULL)    {
        if ((s=strstr (buf, KEY_DDR)) != (char *)NULL)    {
            if (aux_dat != NULL)    {
                if (j++ == 0)
                    *aux_dat = (header *) malloc (sizeof (struct header));
                (void) sdf_ddr (fptr, aux_dat, buf, &nauxs);
            }
        }
        fgets (buf, RECSIZE+1, fptr);
    }
	    
    *nobs = 0;
    *obs_dat = (header *) malloc (sizeof (struct header));
	    
    while ((fgets (buf, RECSIZE+1, fptr) != NULL))    /* Skip blank line(s) */
        if (str_trim (buf, 0) != 0)    break;
    
	/* Start checking the Observation segment.  In case the END OF 
	 * OBSERVATION SEGMENT keyword is missing, read to the end of file, 
	 * then return to the calling routine. 
	 */
    while (strstr (buf, END_OBSERVE) == (char *)NULL)    {
        if ((s=strstr (buf, KEY_DDR)) != (char *)NULL)
            (void) sdf_ddr (fptr, obs_dat, buf, nobs);
        if (fgets (buf, RECSIZE+1, fptr) == NULL)    break;
    }
    fclose (fptr);
    return (nauxs);
}


/****************************************************************************
FUNCTION:
	sdf_ddr ()

PURPOSE/DESCRIPTION:
	Function sdf_ddr scans through the DDR section.  It extracts the record
	name, logical and physical record sizes, and loops through the data 
	variable descriptors within the DDR.
	
	NOTE for the FIRE CI1 SRB data set, only variable descriptors in the 
	record-data-subsection are obtained.  The record-prefix-subsection 
	specifies the fixed variables whose values will be extracted from the 
	data files in the data read routine.

INVOCATION:
	(void) sdf_ddr (fptr, ddr_dat, buf, nrecs)

   WHERE
	<fptr> - I
	   is the file pointer of the header file specified from the 
	   command line argument.
	<ddr_dat> - I, O
	   is the address of the pointer points to the base of a list of 
	   data descriptor records.  It returns the record name, the number 
           of data variables in DDR (Data Descriptor Record), the logical 
	   record size, block size, and either ancillary or observation data 
	   variables list.
	<buf> - I
	   is the buffer containing the first 80 bytes of DDR.
	<nobs> - I, O
	   returns the number of records defined in the observation segment.

	There is NO returned value.

FILE/RECORD REFERENCES:	None.

EXTERNAL ROUTINES: None.

NOTES:
	Because some data sets do not follow the standard data format by 
	padding the blanks in the unused bytes (they are padded with one or 
	multiple "CR" or newline characters), that's why this function can 
	not just count the number of lines read to decide if the data 
	variables section starts.

*****************************************************************************/

void  sdf_ddr (fptr, ddr_dat, buf, nrecs)
FILE  *fptr;
header  **ddr_dat;
char  buf[];
int  *nrecs;
{
	
    char  str[81], end_keyword[80], *s;
    header  *hd;
    int  i, j, tot; 
	
	
    *ddr_dat = (header *) realloc (*ddr_dat, (++(*nrecs))*sizeof (header));
    hd = (*ddr_dat)+(*nrecs-1);
    hd->vd_ptr = (ddr_var *) malloc (sizeof (struct ddr_var));

    /*s = strstr (buf, KEY_DDR)+sizeof(KEY_DDR);
    sscanf (s, "%s", hd->rec_name); */
    /** Skip 14-character "RECORD NAME:  " key word to get the name.  Because
     ** some data sets has blanks in the record name itself, that's why I 
     ** use the strncpy instead of sscanf to extract the name. **/
    strncpy (hd->rec_name, &(buf[14]), 8);
    hd->rec_name[8] = '\0';
#ifndef  SRB
    sscanf (&buf[HD_SIZ_FLDS], "%d %d", &(hd->lrsiz), &(hd->blksiz));
    strcpy (end_keyword, END_VR); 
#else
    sscanf (&buf[SIZ_FLDS],"%d %d %d",&(hd->npfx),&(hd->lrsiz),&(hd->blksiz)); 
    strcpy (end_keyword, END_VAR); 
#endif 

    do    {
        j = str_trim (fgets (buf, RECSIZE+1, fptr), 0);
        if (j == 0)    continue;
    } while (buf[10] != ':');

    for (j = 0; (str_trim (fgets (buf, RECSIZE+1, fptr), 0) > 0) && (j < 2); 
               j++)    ;
    while (j == 0)    /**  Skip the blank lines  **/
        j = str_trim (fgets (buf, RECSIZE+1, fptr), 0);

#ifdef  SRB 
    while ((strstr (buf, DATA_VAR) == (char *)NULL) &&
           (fgets (buf, RECSIZE+1, fptr) != NULL))    ; 
    while ((strstr(buf, VAR_BEGIN) == (char *)NULL) &&
           (fgets (buf, RECSIZE+1, fptr) != NULL))    ; 
    while ((j = str_trim (fgets (buf, RECSIZE+1, fptr), 0)) == 0)    ;

#endif

        /***  The first line of first data variable is passed in.  ***/
    for (hd->nvars = 0, i = 0, tot = 0; (i < hd->lrsiz) && 
                    (tot >= 0); fgets (buf, RECSIZE+1, fptr))    {
        if (strstr (buf, end_keyword) != (char *)NULL) 
            break;
        if ((j = str_trim (buf, 0)) == 0)    {
            j = str_trim (fgets (str, RECSIZE+1, fptr), 0);
            strcpy (buf, str);
        }
        strncpy (&(buf[j]), "            ", (RECSIZE-j));
        if ((j = str_trim (fgets (str, RECSIZE+1, fptr), 0)) == 0)    {
            j = str_trim (fgets (str, RECSIZE+1, fptr), 0);
        }
        strncpy (&buf[RECSIZE], str, j);
        fgets (&(buf[RECSIZE+j]), RECSIZE+1, fptr);
	tot = DDR_var (buf, &(hd->vd_ptr), &(hd->nvars));
        i += tot;
    }
}

/****************************************************************************
FUNCTION:
	DDR_var ()

PURPOSE/DESCRIPTION:
	Function DDR_var extracts short name, type and length, display format,
	scaling constants, and min. max. values for a data variable within a 
	DDR.  It converts the Fortran display format in the header file to C 
	output format string.  Also, the constant b value is decided and 
	returned based on the defined field length.

INVOCATION:
	return_value = DDR_var (string, var_lst, num_vars);

   WHERE
	<string> - I
	   is a 240-byte character string (three logical records) that 
	   contains one variable descriptor.
	<var_lst> - I, O
	   is the address of the pointer points to the base of a list of 
	   data variables.  It returns the updated data variables list.  It 
	   returns the same list if it's an implicit variable.
	<num_vars> - I, O
	   returns the updated number of data variables in the current data 
	   descriptor records.
	<return_value> - O
	   returns the data length of the current data variable.

FILE/RECORD REFERENCES:	None.

EXTERNAL ROUTINES: None.

NOTES:
1.)	In FIRE SDF, if the data values are integer, no conversion is needed.
	Therefore, conversion constant b is set to 0 when the display format 
	is defined as 'integer'.  The read function can use b to decide 
	whether or not the data values are integers.

2.)	If the display format is not one of I, F, E, G, O, or Z, it is 
	converted to floating point format.

*****************************************************************************/

int  DDR_var (string, var_lst, num_vars)
char  *string;
ddr_var  **var_lst;
int  *num_vars;
{ 
    int  i, j;
    char  mnemonic[9], type_len[5], disp_form[9], len_str[8], fmt[12], *s; 
    int  min, max;
    ddr_var  *cur_var;


    for (i = 0, s = &string[24]; i < 4; i++, s++)
	type_len[i] = (isspace (*s)) ? '\0' : *s;

    if ((type_len[0] == '0') && (type_len[1] == '0'))    /* implicit variable */
        return (0);

    *var_lst = (ddr_var *) realloc (*var_lst, (++*num_vars)*sizeof(ddr_var));
    cur_var = (*var_lst)+(*num_vars - 1);

    for (i = 0, s = &string[16]; (i < 8) && (isspace (*s)); i++, s++)    ;
    for (j = 0, s = &string[16+i]; i < 8; i++, j++, s++)    {
	if (isspace (*s))    {
	    cur_var->mnemonic[j] = '\0';
            break;
        }
	cur_var->mnemonic[j] =  *s;
    }

    for (i = 0, s = &string[40]; i < 8; i++, s++)    {
	disp_form[i] = (isspace (*s)) ? '\0' : *s;
    }
    if (strlen (disp_form) > 1)    strcpy (len_str, &(disp_form[1]));
    else    len_str[0] = '\0';

/***  Convert the Fortran display format to C output format string.  ***/

    if ((toupper(disp_form[0]) == 'I') || (toupper(disp_form[0]) == 'F'))
        sprintf (cur_var->disp_form, "%%%s%c", len_str, tolower(disp_form[0]));
        /**  Can't do complex number, convert to double. **/
    else if ((toupper(disp_form[0]) == 'E') || (toupper(disp_form[0]) == 'G'))
        sprintf (cur_var->disp_form, "%%%sl%c", len_str, disp_form[0]);
    else if (toupper(disp_form[0]) == 'O')    /**  Octal  **/
        sprintf (cur_var->disp_form, "%%%s%c", len_str, disp_form[0]);
    else if (toupper(disp_form[0]) == 'Z')    /**  Hexadecimal  **/
        sprintf (cur_var->disp_form, "%%%sX", len_str);
    else     /**  All other formats are converted to double.  **/
        sprintf (cur_var->disp_form, "%%%sG", len_str);

/***  Extract the length (in bytes) of the data variable.  ***/

    if ((toupper(type_len[0]) == 'I') && (type_len[1] == '*'))
        cur_var->size = atoi (&type_len[2]);

/***  Constant b value is decided by the field length. But if the display 
 ***  format is 'integer', b is set to 0 for data read routine to identify.
 ***/
    cur_var->b = 8 * cur_var->size - 1;
    if ((toupper(disp_form[0]) == 'I') || (toupper(disp_form[0]) == 'O')
         || (toupper(disp_form[0]) == 'Z'))
        cur_var->b = 0;

    sscanf (&(string[48]), "%4u", &(cur_var->N));
    sscanf (&(string[52]), "%12d", &(cur_var->A));

    /**  Except for Octal and Hexadecimal, other formats are read in as 
     **  floating point value.  Octal & Hex formats are casted to float.
     **/
    if (toupper(disp_form[0]) == 'Z')    {
        sscanf (&(string[88]), "%12X", &min);
        sscanf (&(string[100]), "%12X", &max);
        cur_var->min = (float) min;
        cur_var->max = (float) max;
    } else if (toupper(disp_form[0]) == 'O')    {
        sscanf (&(string[88]), "%12O", &min);
        sscanf (&(string[100]), "%12O", &max);
        cur_var->min = (float) min;
        cur_var->max = (float) max;
    } else if (toupper(disp_form[0]) == 'I')    {
        sscanf (&(string[88]), "%12d", &min);
        sscanf (&(string[100]), "%12d", &max);
        cur_var->min = (float) min;
        cur_var->max = (float) max;
    } else    {
        sscanf (&(string[88]), "%12f", &(cur_var->min));
        sscanf (&(string[100]), "%12f", &(cur_var->max));
    }

    return (cur_var->size);
}


/****************************************************************************
FUNCTION:
	get_fire_id ()

PURPOSE/DESCRIPTION:
	Function get_fire_id extracts the information from the first two lines 
	in the Fire SDF header file.  These two lines contain the data source 
	and producer's (contact's) name, which uniquely identify the Fire SDF
	data sets.

INVOCATION:
	return_value = get_fire_id (file_name);

   WHERE
	<file_name> - I
	   is the Fire SDF header file name to be processed.
	<return_value> - O
	   returns the index Fire SDF file currently at Langley DAAC.  The 
	   list of available SDF files are defined in the header file (.h).

FILE/RECORD REFERENCES:	None.

EXTERNAL ROUTINES: None.

NOTES:
1.)	This function is added because some Fire SDF data sets are not 
	following the Fire "Standard Data Format".

*****************************************************************************/

int  get_fire_id (filename)
char  *filename;
{

    int  i;
    FILE  *fptr;
    char  buf1[160], buf2[160], *s;


    if ((fptr = fopen (filename, "r")) == NULL)    {
        fprintf (stderr,"ERROR:Can't open FIRE header file:\t%s\n", filename);
        return (-1);
    }

    if ((fgets (buf1, RECSIZE+1, fptr) != NULL) && 
        (fgets (buf2, RECSIZE+1, fptr) != NULL))    {
        for (i = 0; i < (sizeof (sdf_list) / sizeof (sdf_list[0])); i++)   {
            if ((strstr (buf1, sdf_list[i].fire_id) != NULL) && 
                (strstr (buf2, sdf_list[i].producer) != NULL))
                return (i+1); 
        }
    }
}



