#if !NO_SCCS_STRINGS
static char sccs_amort_c[] = "@(#)amort.c	128.1 1/20/93 19:38:40";
#endif /* NO_SCCS_STRINGS */
/********************************************************************\

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

   Module:  amort.c

   Description:  Add-in application that generates an amortization
   table for a fixed-period loan.   

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

/*
   indicate libraries to use, headers to include, and declare
   global variables
*/

#define  LPI_MAIN_MODULE

#define  USE_CELL
#define  USE_RANGE
#define  USE_INPUT
#define  USE_UTILITY
#define  USE_ATFIN
#define  USE_ATSTR

#include "lpixport.h"
#include "amort.h"

LpiRangeType thePrinRange, theIntRange, theTermRange, theOutRange;
LpiCellType  theCell;
LpiMenuType  theMenu;
lbool        IsPrinSet, IsIntSet, IsTermSet, IsOutSet;
lushort      theAppError;
lfloat8      thePrincipal, theInterest, theTerm;

/* utility routine that sounds an error tone */

lsshort amort_bell ()
{
   return (LpiUtBeep(BELL_FREQUENCY,BELL_DURATION));
}

/* utility routine that prompts the user for a range specification */

lsshort get_usr_range (theRange, is_set, thePrompt, theHelp, theHelpLines)
LpiRangeType *theRange;
lbool *is_set;
lmhdl(lmbcs) thePrompt;
lptr(lmhdl(lmbcs)) theHelp;
lslong theHelpLines;
{
   lsshort  theRetCode;
   lbool    valid_range;

   /*
      check if range is valid and construct new range if it is not
      or if range is not already set -- this guarantees that there
      is a valid default range for the next step
   */

   theRetCode = LpiRngGetIsValid(*theRange,&valid_range);
   if (theRetCode != LPI_SUCCESS) return (1);
   if ((!valid_range) || (!(*is_set))) {
      LpiRngDestroy(theRange);
      theRetCode = LpiRngConstructCurrent(theRange,&theAppError);
      if (theRetCode != LPI_SUCCESS) return (1);
   }

   /* prompt the user for a range specification */

   theRetCode = LpiRngInputDefaultHelp(theRange,thePrompt,IS_ANCHORED,
   *theRange,theHelp,&theAppError);
   switch (theRetCode) {
      case LPI_SUCCESS:
         *is_set = LTRUE;   
         break;
      case LPI_K_BREAK:
      case LPI_K_ESCAPE:
         break;
      default:
         return (1);
         break;
   }
   return (0);
}

   /*

      generate an amortization table given the data in the principal,
      interest, and term ranges

   */

lsshort calc_loan ()
{
   lsshort        theRetCode, thePeriods, theLastRow, i;
   lushort        theSheet, theColumn, theRow, 
                  theCurrentColumn, theCurrentRow;
   lmbcs          theWorkfile [LPI_RNG_FILEPATH_BUFLEN];
   lfloat8        thePayment, theBalance, theInt, theCInt,
                  thePrin, theCPrin, thePeriodNum;
   lmhdl(lmbcs)   theChar = LpiLptrToLmhdl(TITLE_SEPARATOR);

   /* extract information from ranges */
 
   theRetCode = LpiRngSelectFloatSheet(thePrinRange,1L,1L,1L,
   &thePrincipal,&theAppError);
   if (theRetCode != LPI_SUCCESS) return (1);

   theRetCode = LpiRngSelectFloatSheet(theIntRange,1L,1L,1L,
   &theInterest,&theAppError);
   if (theRetCode != LPI_SUCCESS) return (1);

   theRetCode = LpiRngSelectFloatSheet(theTermRange,1L,1L,1L,
   &theTerm,&theAppError);
   if (theRetCode != LPI_SUCCESS) return (1);

   /* initialize calculation */

   LpiPushZero();
   LpiDup();
   LpiPopLfloat8(&theCInt);
   LpiPopLfloat8(&theCPrin);
   LpiPushLfloat8(&thePrincipal);
   LpiPopLfloat8(&theBalance);
   theRetCode = LpiAtfinPmt(thePrincipal,theInterest,theTerm,&thePayment);
   if (theRetCode != LPI_SUCCESS) return (1);
   LpiPushLfloat8(&theTerm);
   thePeriods = LpiPopLsShort();
   theLastRow = thePeriods;

   /* locate start of table */

   theRetCode = LpiRngGetFirstCell(theOutRange,theWorkfile,&theSheet,
      &theColumn,&theRow);
   if (theRetCode != LPI_SUCCESS) return (1);
   theRetCode = LpiClConstructWZXY(&theCell,LpiLptrToLmhdl(theWorkfile),
      (lulong)theSheet,(lulong)theColumn,(lulong)theRow);
   if (theRetCode != LPI_SUCCESS) return (1);
   theCurrentColumn = theColumn;
   theCurrentRow = theRow;

   /* write two rows of titles and separators */

   theRetCode = LpiClSetValueString(theCell,theTitlesArray[0]);
   if (theRetCode != LPI_SUCCESS) return (1);
   theRetCode = LpiClSetLabelPrefix(theCell,LPI_T_PREFIX_RIGHT);
   if (theRetCode != LPI_SUCCESS) return (1);
   for (i=1; i<TABLE_COLUMNS; i++) {
      theRetCode = LpiClSetColumn(theCell,++theCurrentColumn);
      if (theRetCode != LPI_SUCCESS) return (1);
      theRetCode = LpiClSetValueString(theCell,theTitlesArray[i]);
      if (theRetCode != LPI_SUCCESS) return (1);
      theRetCode = LpiClSetLabelPrefix(theCell,LPI_T_PREFIX_RIGHT);
      if (theRetCode != LPI_SUCCESS) return (1);
   }
   theCurrentColumn = theColumn;
   theRetCode = LpiClSetColumn(theCell,theCurrentColumn);
   if (theRetCode != LPI_SUCCESS) return (1);
   theRetCode = LpiClSetRow(theCell,++theCurrentRow);
   if (theRetCode != LPI_SUCCESS) return (1);
   
   theRetCode = LpiClSetValueString(theCell,theChar);
   if (theRetCode != LPI_SUCCESS) return (1);
   theRetCode = LpiClSetLabelPrefix(theCell,LPI_T_PREFIX_REPEATING);
   if (theRetCode != LPI_SUCCESS) return (1);
   for (i=1; i<TABLE_COLUMNS; i++) {
      theRetCode = LpiClSetColumn(theCell,++theCurrentColumn);
      if (theRetCode != LPI_SUCCESS) return (1);
      theRetCode = LpiClSetValueString(theCell,theChar);
      if (theRetCode != LPI_SUCCESS) return (1);
      theRetCode = LpiClSetLabelPrefix(theCell,LPI_T_PREFIX_REPEATING);
      if (theRetCode != LPI_SUCCESS) return (1);
   }
   theCurrentColumn = theColumn;
   theRetCode = LpiClSetColumn(theCell,theCurrentColumn);
   if (theRetCode != LPI_SUCCESS) return (1);
   theRetCode = LpiClSetRow(theCell,++theCurrentRow);
   if (theRetCode != LPI_SUCCESS) return (1);
 
   for (i=1; i<=thePeriods; i++) {

      /* period number */

      LpiPushLsShort(i);
      LpiPopLfloat8(&thePeriodNum);
      theRetCode = LpiClSetValueFloat(theCell,thePeriodNum);
      if (theRetCode != LPI_SUCCESS) return (1);
      theRetCode = LpiClSetColumn(theCell,++theCurrentColumn);
      if (theRetCode != LPI_SUCCESS) return (1);

      /* payment */

      theRetCode = LpiClSetValueFloat(theCell,thePayment);
      if (theRetCode != LPI_SUCCESS) return (1);
      theRetCode = LpiClSetColumn(theCell,++theCurrentColumn);
      if (theRetCode != LPI_SUCCESS) return (1);

      /* interest fraction */

      LpiPushLfloat8(&theBalance);
      LpiPushLfloat8(&theInterest);
      LpiMul();
      LpiPopLfloat8(&theInt);

      theRetCode = LpiClSetValueFloat(theCell,theInt);
      if (theRetCode != LPI_SUCCESS) return (1);
      theRetCode = LpiClSetColumn(theCell,++theCurrentColumn);
      if (theRetCode != LPI_SUCCESS) return (1);

      /* principal fraction */

      LpiPushLfloat8(&thePayment);
      LpiPushLfloat8(&theInt);
      LpiSub();
      LpiPopLfloat8(&thePrin);

      theRetCode = LpiClSetValueFloat(theCell,thePrin);
      if (theRetCode != LPI_SUCCESS) return (1);
      theRetCode = LpiClSetColumn(theCell,++theCurrentColumn);
      if (theRetCode != LPI_SUCCESS) return (1);

      /* cumulative interest */

      LpiPushLfloat8(&theCInt);
      LpiPushLfloat8(&theInt);
      LpiAdd();
      LpiPopLfloat8(&theCInt);

      theRetCode = LpiClSetValueFloat(theCell,theCInt);
      if (theRetCode != LPI_SUCCESS) return (1);
      theRetCode = LpiClSetColumn(theCell,++theCurrentColumn);
      if (theRetCode != LPI_SUCCESS) return (1);

      /* cumulative principal */

      LpiPushLfloat8(&theCPrin);
      LpiPushLfloat8(&thePrin);
      LpiAdd();
      LpiPopLfloat8(&theCPrin);

      theRetCode = LpiClSetValueFloat(theCell,theCPrin);
      if (theRetCode != LPI_SUCCESS) return (1);
      theRetCode = LpiClSetColumn(theCell,++theCurrentColumn);
      if (theRetCode != LPI_SUCCESS) return (1);

      /* balance */

      LpiPushLfloat8(&theBalance);
      LpiPushLfloat8(&thePrin);
      LpiSub();
      LpiPopLfloat8(&theBalance);

      theRetCode = LpiClSetValueFloat(theCell,theBalance);
      if (theRetCode != LPI_SUCCESS) return (1);

      /* next row */

      if (i < theLastRow) {
         theCurrentColumn = theColumn;
         theRetCode = LpiClSetColumn(theCell,theCurrentColumn);
         if (theRetCode != LPI_SUCCESS) return (1);
         theRetCode = LpiClSetRow(theCell,++theCurrentRow);
         if (theRetCode != LPI_SUCCESS) return (1);
      }
   }
   return (0);
}

/*  main add-in function */

#if DEBUG
debug_main(LpiContext)
lptr(CONTEXT) LpiContext;
#else
LpiMain(amort_m)
#endif
{
   lsshort        theRetCode, theMenuItem;
   lbool          done = LFALSE;
   lbool          valid_prin, valid_int, valid_term, valid_out;
 
   /* 
      Post Menu: If the user selects one of the three loan input
      values or the output range, then prompt the user for a range
      and return to the menu.  Reset cancels settings.  Go generates
      the payment table.  
   */

   theRetCode = LpiMnuConstruct(&theMenu,amort_theMenuArray,MENU_ITEMS);
   if (theRetCode != LPI_SUCCESS) return;
   theRetCode = LpiMnuSetHelp(theMenu,MENU_HELP_LINES,theMenuHelp);
   if (theRetCode != LPI_SUCCESS) return;

   while (!done) {
      theRetCode = LpiMnuInput(theMenu,&theMenuItem);
      switch (theRetCode) {
         case LPI_SUCCESS:
            break;
         case LPI_K_BREAK:
         case LPI_K_ESCAPE:
            done = LTRUE;
            continue;
            break;
         default:
            return;
            break;
      }
      switch (theMenuItem) {
         case MENU_PRIN:
            if (get_usr_range (&thePrinRange,&IsPrinSet,
            LpiLptrToLmhdl(PRIN_RANGE_PROMPT),
            LpiLptrToLmhdl(thePrincipalHelp),
            PRINCIPAL_HELP_LINES) != 0)
               return;           
            break;
         case MENU_INT:
            if (get_usr_range (&theIntRange,&IsIntSet,
            LpiLptrToLmhdl(INT_RANGE_PROMPT),
            LpiLptrToLmhdl(theInterestHelp),
            INTEREST_HELP_LINES) != 0)
               return;
            break;
         case MENU_TERM:
            if (get_usr_range (&theTermRange,&IsTermSet,
            LpiLptrToLmhdl(TERM_RANGE_PROMPT),
            LpiLptrToLmhdl(theTermHelp),
            TERM_HELP_LINES) != 0)
               return;
            break;
         case MENU_OUTPUT:
            if (get_usr_range (&theOutRange,&IsOutSet,
            LpiLptrToLmhdl(OUT_RANGE_PROMPT),
            LpiLptrToLmhdl(theOutputHelp),
            OUTPUT_HELP_LINES) != 0)
               return;
            break;
         case MENU_RESET:
            IsPrinSet = IsIntSet = IsTermSet = IsOutSet = LFALSE;
            break;
         case MENU_GO:
            /* if all ranges are defined and valid then generate table */

            theRetCode = LpiRngGetIsValid(thePrinRange,&valid_prin);
            if (theRetCode != LPI_SUCCESS) return;
            theRetCode = LpiRngGetIsValid(theIntRange,&valid_int);
            if (theRetCode != LPI_SUCCESS) return;
            theRetCode = LpiRngGetIsValid(theTermRange,&valid_term);
            if (theRetCode != LPI_SUCCESS) return;
            theRetCode = LpiRngGetIsValid(theOutRange,&valid_out);
            if (theRetCode != LPI_SUCCESS) return;

            if (IsPrinSet && IsIntSet && IsTermSet && IsOutSet &&
            valid_prin && valid_int && valid_term && valid_out) {
               if (calc_loan() != 0) return;
               else done = LTRUE;
            }
            else {
               amort_bell();
            }
            break;
         case MENU_QUIT:
            done = LTRUE;
            break;
         default:
            break;
      }
   }
   return;
}

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

   The following function, LpiBind, is called when 1-2-3 loads our add-in.
   Use this function to perform any setup, such as allocating memory,
   opening files, etc.

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

#if DEBUG
debug_bind(LpiContext, AltExports)
lptr(CONTEXT) LpiContext;
LpiAltExport  AltExports;
#else
LpiBind(amort_b)
#endif
{
   /* Start loading pointers to used modules */

   LPI_SETUP_LOAD;

   if (LPI_LOAD_ATFIN   != LPI_SUCCESS) LpiSignal (LPI_FAILURE);
   if (LPI_LOAD_ATSTR   != LPI_SUCCESS) LpiSignal (LPI_FAILURE);
   if (LPI_LOAD_INPUT   != LPI_SUCCESS) LpiSignal (LPI_FAILURE);
   if (LPI_LOAD_CELL    != LPI_SUCCESS) LpiSignal (LPI_FAILURE);
   if (LPI_LOAD_RANGE   != LPI_SUCCESS) LpiSignal (LPI_FAILURE);
   if (LPI_LOAD_UTILITY != LPI_SUCCESS) LpiSignal (LPI_FAILURE);

   /* initialize objects and ranges */

   if ((LpiRngConstructCurrent(&thePrinRange,&theAppError)) != LPI_SUCCESS)
      LpiSignal (LPI_FAILURE);
   if ((LpiRngConstructCurrent(&theIntRange, &theAppError)) != LPI_SUCCESS)
      LpiSignal (LPI_FAILURE);
   if ((LpiRngConstructCurrent(&theTermRange,&theAppError)) != LPI_SUCCESS)
      LpiSignal (LPI_FAILURE);
   if ((LpiRngConstructCurrent(&theOutRange, &theAppError)) != LPI_SUCCESS) 
      LpiSignal (LPI_FAILURE);
   IsPrinSet = IsIntSet = IsTermSet = IsOutSet = LFALSE;
   theCell = LNULL;
   theMenu = LNULL;

   LpiSignal (LPI_SUCCESS);
}

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

   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 clean up such as
   deallocating memory you allocated in LpiBind, closing opened files, etc.

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

#if DEBUG
debug_unbind(LpiContext)
lptr(CONTEXT) LpiContext;
#else
LpiUnbind(amort_u)
#endif
{
   /* destroy objects, if still active */

   if (thePrinRange != LNULL) LpiRngDestroy(&thePrinRange);
   if (theIntRange  != LNULL) LpiRngDestroy(&theIntRange);
   if (theTermRange != LNULL) LpiRngDestroy(&theTermRange);
   if (theOutRange  != LNULL) LpiRngDestroy(&theOutRange);
   if (theCell      != LNULL) LpiClDestroy(&theCell);
   if (theMenu      != LNULL) LpiMnuDestroy(&theMenu);

   LPI_UNLOAD;                                     /* macro for cleanup */

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




