#if !NO_SCCS_STRINGS
static char sccs_newavg_c[] = "@(#)newavg.c	128.1 1/20/93 19:39:14";
#endif /* NO_SCCS_STRINGS */
/***********************************************************************\

   (c) Copyright 1991 by Lotus Development Corporation
   All Rights Reserved

   Module:  newavg.c

   Description:   This module defines the add-in @function NEWAVG.
   @NEWAVG returns the arithmetic mean of a range of values.  @NEWAVG
   differs slightly from 1-2-3's @AVG function.

\***********************************************************************/

#define  LPI_MAIN_MODULE    /* identify this module as the primary one */

#define USE_REGISTER    /* incorporate routines to register @functions */
#define USE_RANGE    /* incorporate routines to access Range functions */
#define USE_CELL      /* incorporate routines to access Cell functions */

#include "lpixport.h"          /* incorporate Lotus header information */

#define  NEWAVG_NAME  "NEWAVG"

/***********************************************************************\

The following function, newavg, returns the arithmetic mean of values
in a range.  @NEWAVG ignores label and blank cells.  @NEWAVG returns ERR,
if any cell in the range contains ERR.  @NEWAVG returns NA, if no cells
contain ERR and one or more cells contain NA.  @NEWAVG is similar to
1-2-3's @AVG function, except that @AVG assigns zero as the value for
label cells.

Certain statistical applications may require that you further distinguish
between ERR and NA cells.  For example, you may wish to ignore cells that
contain NA.  Refer to the comments below for information on how to do this.
The behavior of @NEWAVG, as is, is a model for handling ERR and NA values
in a manner consistent with 1-2-3 and the 1-2-3 user's expectations.

@NEWAVG accepts one or more arguments.  Arguments can be float numbers
or ranges.  If the input is a range, @NEAVG constructs a cell at each
location in the range, determines the cell's data type, and performs the
appropriate action as described above.  If the input is a number, @NEAVG
simply adds the number to the current sum and increments the number of
values in the sum.

@NEWAVG processes input arguments until none are left, an ERR cell is
processed, or an error occurs.  If no ERRs or errors are encountered,
@NEWAVG sets the result to NA, if an NA cell is encountered, or
divides the sum by the number of values in the sum to yield the average.

If an ERR cell is found or an error occurs, execution transfers to error
handler statements.  These statments remove any items from the stack and
set the return to ERR.

Note: @NEWAVG processes all the values in a range by constructing a Cell
at each location, processing the Cell, and destroying it.  This is similar
to, and more straightforward than, creating a single Cell and changing
its sheet, column, and row.  Alternatively, we could use LpiRngGetDataType
and LpiRngIndexFloat or LpiRngSelectFloat to type and extract values from
a range.  While this approach would require less preparation, the
performance is slower than the cell method.

\**********************************************************************/

lsshort newavg (RegContext, arg_count, LpiContext)
   LpiRegContextType   RegContext;   /* required registration context */
   lushort             arg_count;        /* number of input arguments */
   lptr(CONTEXT)       LpiContext;                /* required context */
{
   lfloat8      theInputFloat;         /* storage for float arguments */

   LpiRangeType theRange;                       /* Range ADT variable */
   lushort      theNumSheets;
   lushort      theNumColumns;
   lushort      theNumRows;                       /* range dimensions */
   lmbcs        theWorkfileName[LPI_RNG_FILEPATH_BUFLEN];

   lushort      s, c, r;         /* range scannning for loop counters */
   LpiCellType  theCell;                         /* Cell ADT variable */
   lfloat8      theValue;                  /* storage for cell values */
   lsshort      theDataType;                        /* cell data type */
   lushort      theArgType;                     /* argument data type */

   lushort      theFirstSheet;   /* variables for first cell position */
   lushort      theFirstColumn;
   lushort      theFirstRow;

   lushort      i;            /* argument processing for loop counter */
   lfloat8      theAverage;                     /* storage for result */
   lsshort      retval;                       /* function return code */
   lslong       theCount;                    /* number of data points */
   lbool        NA_data_found;  /* flag that is set if NA cells found */

   NA_data_found = LFALSE;          /* intially no NA cells are found */
   theCount = 0L;          /* initialize the number of values to zero */
   LpiPushZero();                                /* start sum at zero */
   for (i = 0; i < arg_count; i++)          /* loop for each argument */
   {
      /* get type of next argument */
      retval = LpiRegGetArgType(i,&theArgType);
      if (retval != LPI_SUCCESS) goto error;

      switch (theArgType)
      {
         case LPI_T_ARG_TYPE_RNG:
            /* get the range argument input */
            retval = LpiRegGetArgRange(i, &theRange);
            if (retval != LPI_SUCCESS) goto error;
      
            /* get the range's front top left cell coordinates */
            retval = LpiRngGetFirstCell(theRange, theWorkfileName,
               &theFirstSheet, &theFirstColumn, &theFirstRow);
            if (retval != LPI_SUCCESS) goto error;

            /* get number of sheets in the range */
            retval = LpiRngGetSheets(theRange, &theNumSheets);
            if (retval != LPI_SUCCESS) goto error;

            /* get number of columns in the range */
            retval = LpiRngGetColumns(theRange, &theNumColumns);
            if (retval != LPI_SUCCESS) goto error;

            /* get number of rows in the range */
            retval = LpiRngGetRows(theRange, &theNumRows);
            if (retval != LPI_SUCCESS) goto error;

            theNumSheets--;       /* decrement all three dimensions */
            theNumColumns--;          /* to support zero-based loop */
            theNumRows--;                      /* counter variables */

            /* loop through the range by row, column, and sheet */
            for (s = 0; s <= theNumSheets; s++)
            {
               for (c = 0; c <= theNumColumns; c++)
               {
                  for (r = 0; r <= theNumRows; r++)
                  {
                     /* construct a cell at the next range location */
                     retval = LpiClConstructWZXY(&theCell, theWorkfileName,
                        theFirstSheet+s, theFirstColumn+c, theFirstRow+r);
                     if (retval != LPI_SUCCESS)
                     {
                        LpiRngDestroy(&theRange);
                        goto error;
                     }
      
                     /* determine the type of data in the cell */
                     retval = LpiClGetDataType(theCell,&theDataType);
                     if (retval != LPI_SUCCESS)
                     {
                        LpiClDestroy(&theCell);
                        LpiRngDestroy(&theRange);
                        goto error;
                     }
      
                     switch (theDataType)
                     {
                        case  LPI_T_DATA_BLANK:
                        case  LPI_T_DATA_STRINGDATA:
                           break;
                        case  LPI_T_DATA_NADATA:
                           NA_data_found = LTRUE;
                           break;
                        case  LPI_T_DATA_ERRDATA:
                           LpiClDestroy(&theCell);
                           LpiRngDestroy(&theRange);
                           goto error;
                           break;
                        case  LPI_T_DATA_NUMBERDATA:
                           retval = LpiClGetValueFloat(theCell,&theValue);
                           if (retval != LPI_SUCCESS)
                           {
                              LpiClDestroy(&theCell);
                              LpiRngDestroy(&theRange);
                              goto error;
                           }
                           LpiPushLfloat8(&theValue);
                           LpiAdd();
                           theCount++;
                           break;
                        default:
                           break;
                     }
                  LpiClDestroy(&theCell);
                  }
               }
            }
            LpiRngDestroy(&theRange);
            break;

         case LPI_T_ARG_TYPE_NUM:
            /* add the input value to the current sum */
            retval = LpiRegGetArgNumFloat(i, &theInputFloat);
            if (retval != LPI_SUCCESS) goto error;
            LpiPushLfloat8(&theInputFloat);
            LpiAdd();
            theCount++;
            break;

         case LPI_T_ARG_TYPE_STR:
            /* ignore strings */
            break;

         case LPI_T_ARG_TYPE_NA:
            /* set flag to indicate an NA cell was found */
            NA_data_found = LTRUE;
            break;

         case LPI_T_ARG_TYPE_MSG:
            /* ignore missing argument */
            break;

         case LPI_T_ARG_TYPE_ERR:
         default:
            goto error;
            break;                        
      }
   }  

   /* if an NA cell was found, clear the stack and set return to NA */
   if (NA_data_found)
   {
      LpiDropOne();
      LpiRegAtSetRetType(LPI_T_REG_AT_TYPE_NA);
   }
   else
   {
      /* divide by the count, if necessary */
      if (theCount > 1)
      {
         LpiPushLsLong(theCount);
         LpiDiv();
      }
      /* pop the average off the stack */
      LpiPopLfloat8(&theAverage);

      /* set the return to the average */
      LpiRegAtSetRetType(LPI_T_REG_AT_TYPE_NUM_FLOAT);
      * ((lptr(lfloat8))LpiRegAtAccessRetData()) = theAverage;
   }
   return LPI_SUCCESS;

/* process errors by clearing the stack and setting the return to ERR */
error:
   LpiDropMany(LpiStackDepth());
   LpiRegAtSetRetType(LPI_T_REG_AT_TYPE_ERR);
   return LPI_SUCCESS;
}
 
/************************************************************************\

   The following function, LpiBind, is called when 1-2-3 loads our add-in.
   In this function we load the Register module and register our @function.
   Use this function to perform any setup, such as allocating memory,
   opening files, etc.

\************************************************************************/

LpiBind(newavg_b)
{
   lsshort retval;                              /* function return code */
   lushort arg_types[6];          /* array of argument types, one entry */
                  
   /* @NEWAVG supports 1..6 arguments, inclusive; each argument
      can be either a float or a range */

   arg_types[0] = LPI_T_ARG_TYPE_NUM | LPI_T_ARG_TYPE_RNG;
   arg_types[1] = LPI_T_ARG_TYPE_NUM | LPI_T_ARG_TYPE_RNG;
   arg_types[2] = LPI_T_ARG_TYPE_NUM | LPI_T_ARG_TYPE_RNG;
   arg_types[3] = LPI_T_ARG_TYPE_NUM | LPI_T_ARG_TYPE_RNG;
   arg_types[4] = LPI_T_ARG_TYPE_NUM | LPI_T_ARG_TYPE_RNG;
   arg_types[5] = LPI_T_ARG_TYPE_NUM | LPI_T_ARG_TYPE_RNG;

   LPI_SETUP_LOAD;      /* required before loading zero or more modules */

   if (LPI_LOAD_REGISTER != LPI_SUCCESS)        /* load Register module */
      LpiSignal (LPI_FAILURE);
   if (LPI_LOAD_RANGE != LPI_SUCCESS)              /* load Range module */
      LpiSignal (LPI_FAILURE);
   if (LPI_LOAD_CELL != LPI_SUCCESS)                /* load Cell module */
      LpiSignal (LPI_FAILURE);

   retval = LpiRegAtfunc(
      NEWAVG_NAME,                 /* @function name in a #define above */
      newavg,             /* name of function that implements @function */
      1,                                   /* number of input arguments */
      arg_types,                       /* array of input argument types */ 
      LNULL,                                    /* registration context */
      LPI_T_REG_AT_VARARGS |           /* register to accept a variable */
      LPI_T_REG_AT_PASS_MSG,   /* number of arguments, including blanks */
      LPI_T_REG_AT_TYPE_NUM_FLOAT |       /* returns number, ERR, or NA */
      LPI_T_REG_AT_TYPE_ERR |                                  
      LPI_T_REG_AT_TYPE_NA);  

   /* if registration fails, indicate an error */
   if (retval != LPI_SUCCESS) LpiSignal (LPI_FAILURE); 

   LpiSignal (LPI_SUCCESS);             /* otherwise indicate no errors */
}

/************************************************************************\

   The following function, LpiUnbind, is called when 1-2-3 unloads our
   add-in.  This function simply executes the macro LPI_UNBIND and
   signals no errors.  Use this function to perform any cleanup, such as
   deallocating memory you allocated in LpiBind, closing opened files, etc.

   Note: You do not have to explicitly unregister an @function.  When you
   unload the add-in that defines the function, 1-2-3 automatically
   unregisters any @functions the add-in defined.  If you attempt to enter
   an @function that has not yet been registered, 1-2-3's cell editor
   beeps. All existing formulas that contain add-in @functions that are
   currently unregistered resolve to ERR.  The @functions will resolve
   properly the next time the add-in registers the @function.

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

LpiUnbind(newavg_u)
{
   LPI_UNLOAD;                                     /* macro for cleanup */

   LpiSignal(LPI_SUCCESS);                        /* indicate no errors */
}




