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

   Copyright (c) 1991, Lotus Development Corporation                    
   1-2-3 for OS/2 Sample Custom @Function and Macro Command

   Module:        lepdemo.c
   Description:   C source file for lepdemo.dll
   Macro {add} adds a value to empty cells and values in a range.
   @Function @newavg calculates the average of list of values, 
   ignoring blanks and labels.  

****************************************************************************/
#define  INCL_WIN
#define  INCL_DOS
#include <os2.h>
#include <lep.h>
#include <math.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

/****************************************************************************
   Function prototypes.
****************************************************************************/
VOID     LEPC_API AddCommand( PVOID pData, USHORT cArguments );
VOID     LEPC_API NewAverage( PVOID pData, USHORT cArguments );
USHORT   LEPC_API RegisterAddCommand( VOID );
USHORT   LEPC_API RegisterNewaverage( VOID );
USHORT   LEPC_API RegisterStringReverse( VOID );
VOID     LEPC_API StringReverse( PVOID pData, USHORT cArguments );

/****************************************************************************
   Library Manager Function - 1-2-3 calls this function in 
   response to {library-attach} and {library-detach}.  If you 
   need to allocate resources that are visible across multiple
   add-in invocations, do so in the switch branch that handles
   the LEPC_MSG_ATTACH message and free these resources in the
   LEPC_MSG_DETACH branch.
****************************************************************************/

#pragma handler(LEPDEMOLibraryManager)
PVOID LEPC_API LEPDEMOLibraryManager( PVOID pData, USHORT usMsg)
{
/* Make these static, so they'll be preserved across calls. */
static HAB hab;
static HMQ hmq;

/* Remove compiler warnings */
   pData;
   switch( usMsg )
   {
      case LEPC_MSG_ATTACH:
/* Create message queue to allow calling WinMessageBox and similar functions */
         hab = WinInitialize(0L);
			hmq = WinCreateMsgQueue(hab,0);
			RegisterAddCommand();
         RegisterNewaverage();
         RegisterStringReverse();
         break;

      case LEPC_MSG_DETACH:
/* Don't forget to destroy the queue! */
			WinDestroyMsgQueue(hmq);
         break;
   }
   return( NULL );
}

/****************************************************************************
   Registration Functions - called by Library Manager Function
   in response to {library-attach}.  
   The macro command is {add} and accepts two input arguments, 
   a range and an optional value.  Implementation resides in 
   the function AddCommand.
   The @function will be called @newavg.  The function's 
   implementation resides in the function NewAverage.
****************************************************************************/
USHORT LEPC_API RegisterAddCommand( VOID )
{
   PVOID    pData;
   USHORT   ausPrototype[2];

   ausPrototype[0] = LEPC_PROT_TYPE_RANGE;
   ausPrototype[1] = LEPC_PROT_TYPE_NUMBER | LEPC_PROT_OPT_OPTIONAL;

   return( LepMcRegisterCommand( "add", 
                                 AddCommand, 
                                 0, 
                                 2, 
                                 ausPrototype, 
                                 &pData, 
                                 NULL ) );
}

USHORT LEPC_API RegisterNewaverage( VOID )
{
   return( LepAfRegisterFunction( "newavg", 
                                  NewAverage,   
                                  0,   
                                  NULL,   
                                  NULL ) );
}

USHORT LEPC_API RegisterStringReverse( VOID )
{
   return( LepAfRegisterFunction( "reverse", 
                                  StringReverse,   
                                  0,   
                                  NULL,   
                                  NULL ) );
}

/****************************************************************************
   AddCommand Function - 1-2-3 calls this function in response 
   to an {add} command.  Function gets a range from the user 
   and checks for a number argument.  The function increaments 
   each value in the range by the second argument's value (1 is
   used if no argument is supplied).
****************************************************************************/
VOID LEPC_API AddCommand( PVOID pData, USHORT cArguments )
{
   PVOID       pRange;
   double      dFactor, dValue;
   USHORT      usType, usSize, usResult;
   USHORT      ausDimen[ LEPC_COORD_DIMEN ];
   USHORT      ausCoord[ LEPC_COORD_DIMEN ];

/****************************************************************************
   Get handle for first argument range. Return if not successful.
****************************************************************************/
   usResult = LepMcGetArgValue( 1, 
                                LEPC_TYPE_RANGE, 
                                sizeof( PVOID ), 
                                &pRange );

   if ( usResult != LEPC_RET_SUCCESS )
      return;

/****************************************************************************
   Get factor to add to each value in the range.  Use default
   value to add if user omits argument.
****************************************************************************/
   usResult = LepMcGetArgValue( 2, 
                                LEPC_TYPE_DOUBLE, 
                                sizeof( double ), 
                                &dFactor );

   if ( usResult != LEPC_RET_SUCCESS ) 
      dFactor = 1.0;

/****************************************************************************
   Get range dimensions: number of sheets, columns, and rows. 
   Then process entire range by row, by column, and by sheet.
****************************************************************************/
   LepMcGetRangeDimen( pRange, ausDimen );

   for ( ausCoord[ LEPC_COORD_SHEET ] = 1;
         ausCoord[ LEPC_COORD_SHEET ] <= ausDimen[ LEPC_COORD_SHEET ];
         ausCoord[ LEPC_COORD_SHEET ]++ ) 
   {
      for ( ausCoord[ LEPC_COORD_COLUMN ] = 1;
            ausCoord[ LEPC_COORD_COLUMN ] <= ausDimen[ LEPC_COORD_COLUMN ];
            ausCoord[ LEPC_COORD_COLUMN ]++ ) 
      {
         for ( ausCoord[ LEPC_COORD_ROW ] = 1;
               ausCoord[ LEPC_COORD_ROW ] <= ausDimen[ LEPC_COORD_ROW ];
               ausCoord[ LEPC_COORD_ROW ]++ )
         {
/****************************************************************************
   Add value of second argument or default value to cell if it
   contains a value or it is empty.  Ignore labels, @NA and @ERR.
****************************************************************************/
            LepMcGetCellType( pRange, ausCoord, &usType, &usSize );
            switch( usType )
            {
               case LEPC_TYPE_TREAL:
                  LepMcGetCellValue( pRange, 
                                     ausCoord,
                                     LEPC_TYPE_DOUBLE,
                                     sizeof( double ),
                                     &dValue );
                  dValue += dFactor;
                  LepMcSetCellValue( pRange,
                                     ausCoord,
                                     LEPC_TYPE_DOUBLE,
                                     sizeof( double ),
                                     &dValue );
                  break; 

               case LEPC_TYPE_EMPTY:
                  LepMcSetCellValue( pRange, 
                                     ausCoord,
                                     LEPC_TYPE_DOUBLE,
                                     sizeof( double ),
                                     &dFactor );
                  break;

               case LEPC_TYPE_STRING:
               case LEPC_TYPE_NA:
               case LEPC_TYPE_ERR:
               default:
                  break;
            }
         }
      }
   }
   return;
}

/****************************************************************************
   NewAverage Function - 1-2-3 calls this function to evaluate
   @newavg.  The function accepts numbers, ranges, and 
   collections.  NewAverage returns the average of all numeric
   values passed directly as input and all numeric cells in
   any ranges or collections the user provides. Note: This 
   function keeps track of any ERR and NA values.  If @ERR is 
   found, @newavg  returns ERR.  If @NA is found and @ERR is
   not found, @newavg returns NA.
****************************************************************************/
VOID LEPC_API NewAverage( PVOID pData, USHORT cArguments )
{
   PVOID       pRange, pLoop;
   USHORT      usType, usSize, cusArgs;
   double      dSum, dValue;
   LONG        lCount;
   BOOL        fFoundNA;
   USHORT      ausDimen[ LEPC_COORD_DIMEN ];
   USHORT      ausCoord[ LEPC_COORD_DIMEN ];

/****************************************************************************
   Initalize Accumulator - dSum, Counter - lCount, and 
   @NA Found Flag - fFoundNA.
****************************************************************************/
   dSum        = 0.0;
   lCount      = 0;
   fFoundNA    = 0;

/****************************************************************************
   Get each argument to the @function. Return @ERR if an error
   is encountered. Process each argument by determining its type.
   Ignore blanks and labels.  For numbers, add the value to the
   current total and increment the count. For @ERR, end processing
   immediately, returning @ERR.  For @NA, set FoundNA flag.  Return @NA on 
   exit if no @ERR found. For ranges and collections, process 
   each cell according to the above rules.
****************************************************************************/
   for ( cusArgs=1; cusArgs <= cArguments; cusArgs++ )
   {
      if ( LepAfGetArgType( cusArgs, &usType, &usSize ) != LEPC_RET_SUCCESS )
         LepAfReturnValue( LEPC_TYPE_ERR, 0, NULL );

/****************************************************************************
   If an argument is a range, extract a handle to the range -
   pRange.  Determine the range's dimensions.  Scan the range
   by sheet, column, and row.  Accumulate the value of numeric 
   cells and increment the count of accumulated cells.
****************************************************************************/
      switch( usType )
      {
         case LEPC_TYPE_RANGE:
            LepAfGetArgValue( cusArgs, 
                              LEPC_TYPE_RANGE, 
                              sizeof( PVOID ), 
                              &pRange );

            LepAfGetRangeDimen( pRange, ausDimen );

            for ( ausCoord[ LEPC_COORD_SHEET ] = 1;
                  ausCoord[ LEPC_COORD_SHEET ] 
                  <= ausDimen[ LEPC_COORD_SHEET ];
                  ausCoord[ LEPC_COORD_SHEET ]++ )
            {
               for ( ausCoord[ LEPC_COORD_COLUMN ] = 1;
                     ausCoord[ LEPC_COORD_COLUMN ] 
                     <= ausDimen[ LEPC_COORD_COLUMN ];
                     ausCoord[ LEPC_COORD_COLUMN ]++ )
               {
                  for ( ausCoord[ LEPC_COORD_ROW ] = 1;
                        ausCoord[ LEPC_COORD_ROW ] 
                        <= ausDimen[ LEPC_COORD_ROW ];
                        ausCoord[ LEPC_COORD_ROW ]++ )
                  {

/****************************************************************************
   Determine the type of the current cell.
****************************************************************************/
                     LepAfGetCellType( pRange, 
                                       ausCoord,
                                       &usType,
                                       &usSize );
                     switch( usType )
                     {
/****************************************************************************
   Accumulate numeric value - dValue - into dSum and increment 
   lCount for each cell containing a value.
****************************************************************************/
                        case LEPC_TYPE_TREAL:
                           LepAfGetCellValue( pRange,
                                              ausCoord,
                                              LEPC_TYPE_DOUBLE,   
                                              sizeof( double ),
                                              &dValue );
                           dSum += dValue;
                           lCount++;
                           break; 

/****************************************************************************
   Ignore labels and blanks.  If @NA is encountered set fFoundNA
   flag and continue processing through range.  If @ERR is 
   encountered return @ERR as result.
****************************************************************************/
                        case LEPC_TYPE_EMPTY:
                        case LEPC_TYPE_STRING:
                           break;  

                        case LEPC_TYPE_NA:
                           fFoundNA = 1;
                           break;

                        case LEPC_TYPE_ERR:
                        default:
                           LepAfReturnValue( LEPC_TYPE_ERR, 0, NULL );
                           return;
                     }
                  }
               }
            }
              break;

/****************************************************************************
   Process a collection using the range-loop access method.  
   Start by getting a handle - pRange - to the collection.  Scan
   the entire collection and accumulate the value of numeric
   valued cells and increment the count of accumulated cells.
****************************************************************************/
         case LEPC_TYPE_COLLECTION:
            LepAfGetArgValue( cusArgs, 
                              LEPC_TYPE_COLLECTION,
                              sizeof( PVOID ),
                              &pRange );

            LepAfBeginRangeLoop( pRange, LEPC_OPT_LOOP_NONE, &pLoop );
            while ( LepAfNextLoopCell( pLoop ) != LEPC_RET_LOOP_DONE )
            {
               LepAfGetLoopType( pLoop, &usType, &usSize );
               switch( usType )
               {
/****************************************************************************
   Accumulate numeric cell value - dValue - into dSum and 
   increment lCount for each cell containing a value.
****************************************************************************/
                  case LEPC_TYPE_TREAL:
                     LepAfGetLoopValue( pLoop, 
                                        LEPC_TYPE_DOUBLE,
                                        sizeof( double ),
                                        &dValue );
                     dSum += dValue;
                     lCount++;
                     break; 

/****************************************************************************
   Ignore labels and blanks.  If @NA is encountered set fFoundNA
   flag and continue processing through range. If @ERR is 
   encountered return @ERR as result.
****************************************************************************/
                  case LEPC_TYPE_EMPTY:
                  case LEPC_TYPE_STRING:
                     break;  

                  case LEPC_TYPE_NA:
                     fFoundNA = 1;
                     break;

                  case LEPC_TYPE_ERR:
                  default:
                     LepAfReturnValue( LEPC_TYPE_ERR, 0, NULL );
                     return;   
               }
            }
            LepAfEndRangeLoop( pLoop );
            break;

/****************************************************************************
   Process individual argument list values. Accumulate numeric 
   value (dValue) into dSum and increment lCount for each cell 
   containing a value.
****************************************************************************/
         case LEPC_TYPE_TREAL:
            LepAfGetArgValue( cusArgs, 
                              LEPC_TYPE_DOUBLE,
                              sizeof( double ),
                              &dValue );
            dSum += dValue;
            lCount++;
            break; 

/****************************************************************************
   Ignore labels and blanks.  If @NA is
   encountered set fFoundNA flag and continue processing 
   through range. If @ERR is encountered return @ERR as result.
****************************************************************************/
         case LEPC_TYPE_EMPTY:
         case LEPC_TYPE_STRING:
            break;  

         case LEPC_TYPE_NA:
            fFoundNA = 1;
            break;

         case LEPC_TYPE_ERR:
         default:
            LepAfReturnValue( LEPC_TYPE_ERR, 0, NULL );
            return;   
        }
   }

/****************************************************************************
   Finnished processing all arguments. Return @NA if any cells
   containing @NA were encountered, otherwise calculate average
   and return value.
****************************************************************************/
   if ( fFoundNA )
   {
      LepAfReturnValue( LEPC_TYPE_NA, 0, NULL );
   }

/****************************************************************************
   Prevent DIVISION BY ZERO error by verifying divisor (lCount)
   is not equal to zero.  If lCount is equal to zero do not 
   perform division and return 0 as result.
****************************************************************************/
   else 
   {
      ( lCount == 0 ) ? ( dValue = 0.0 ) : ( dValue = dSum / lCount );
        LepAfReturnValue( LEPC_TYPE_DOUBLE, sizeof( double ), &dValue );
   }
   return;
}

/****************************************************************************
String Reverse function: Used for @reverse function. Reverses the case of
characters in a string. Other data types return ERR.
****************************************************************************/
#pragma handler(StringReverse)
VOID LEPC_API StringReverse( PVOID pData, USHORT cArguments )
{
   USHORT      usType, usSize;
	UCHAR			stringbuffer[LEPC_MAX_STRING];
	PUCHAR		cp;


	if ( LepAfGetArgType( 1, &usType, &usSize ) != LEPC_RET_SUCCESS )
		LepAfReturnValue( LEPC_TYPE_ERR, 0, NULL );
	switch( usType )
	{
			case LEPC_TYPE_STRING:
				usSize = min(usSize, LEPC_MAX_STRING);									/* Make sure we don't overrun. */
				LepAfGetArgValue(1, LEPC_TYPE_STRING, usSize, stringbuffer);	/* Get argument */
				for (cp = stringbuffer; *cp; cp++)										/* Actually do some work, here. */
					*cp = (isupper(*cp) ? tolower(*cp) : toupper(*cp));
				LepAfReturnValue( LEPC_TYPE_STRING,										/* Return the result */
					strlen(stringbuffer) + 1,												/* Length for LEP is strlen + 1 */
					stringbuffer);	
				return;
			case LEPC_TYPE_RANGE:
			case LEPC_TYPE_COLLECTION:
			case LEPC_TYPE_EMPTY:
			case LEPC_TYPE_TREAL:
			case LEPC_TYPE_NA:
			case LEPC_TYPE_ERR:
			default:
				LepAfReturnValue( LEPC_TYPE_ERR, 0, NULL );	/* Can't reverse any of these, return ERR */
				return;
	}
	return;
}
