/* DEC	*_CompoundAux(fun, nperi, nperd, intr, pv, pmt,	fv, begend, opt)
 *
 * ARGUMENT
 *	int	fun, *nperi, begend, opt;
 *	DEC	*nperd,	*intr, *pv, *pmt, *fv;
 *
 * DESCRIPTION
 *	Given four variables involved in compound interest, solves for the
 *  fifth one.	The variables are the number of	periods	nper, the percentage
 *  interest rate per period intr, the present value pv, the periodic payment
 *  pmt, and the future	value fv.  begend specifies whether payments take
 *  place at the beginning or the end of each month, while opt tells which
 *  variable to	solve for.
 *	This auxillary function	does the work of CompoundInterest,
 *  CompoundInterestSimple, CompoundInterestCompound,
 *  and	AdvancePayment.
 *
 * SIDE	EFFECTS
 *	Changes	value of unknown variable.
 *
 * RETURNS
 *	In case	of success when	not solving for	nper, the result is returned.
 *  If solving for nper	or if an error occurs, GM_NULL is returned.
 *
 * POSSIBLE ERRORS
 *	GM_NULLPOINTER
 *	GM_ARGVAL
 *
 *
 *
 * AUTHOR
 *  Jared Levy
 *   Copyright (C) 1988-1990 Greenleaf Software	Inc.  All rights reserved.
 *
 * ALGORITHM
 *	Solve the following formula for	the unknown variable:
 *  0=pv*alpha + (1+intr*begend)*pmt*[1-(1+intr)^-int(nper)]/intr +
 *	 fv*(1+intr)^-int(nper)
 *  where alpha=1 for no odd period,
 *	  alpha=1+intr*frac(nper) for an odd period with simple	interest
 *	  alpha=(1+intr)^frac(nper) for	an odd period with compound interest
 *
 *  If intr==0,	the function solves
 *	0 = pv + nper *	pmt + fv
 *  If nper==0,	the function solves
 *	0 = pv * alpha + fv
 *
 *  The	equation can be	solved in closed form for any variable except intr,
 *  which requires numerical methods.
 *  The	variables used by this routine have the	following meanings:
 *	fun:   1 if called by ComoundInterest, 2 if by CompoundInterestSimple,
 *		3 if by	CompoundInterestCompound, and 0	if by AdvancePayment
 *	nperi: integer number of periods if fun==1
 *	nperd: DEC number of periods if	fun==2 or fun==3
 *	intr:  interest	rate per period
 *	pv:    present value
 *	pmt:   period payment
 *	fv:    future value (balloon payment)
 *	begend:	GM_BEGIN if payments at	beginning of period, GM_END if at end
 *		(or if fun==0, number of advanced payments ad)
 *	opt:   variable	to solve for: GM_N, GM_PV, GM_INTR, GM_FV, GM_PMT
 *
 *	opi:   1 + intr
 *	intper:	integer	part of	nper
 *	fractper: fractional part of nper (if fun==2 or	fun==3)
 *	opitmn:	(1 + intr) ^ -intper
 *	mess: (1 + begend)[1 - (1 + intr) ^ -intper]/intr
 *	alpha: 1 + intr	* fractper if fun==2, (1 + intr) ^ fractper if fun==3
 *	pva:   pv * alpha
 *	fvopi: fv * opitmn
 *	pmtm:  pmt * mess
 *	temp, temp2: temporary DEC's
 */

#include <stdio.h>
#include "gm.h"
#include "gmsystem.h"

DEC	*_CompoundAux(fun, nperi, nperd, intr, pv, pmt,	fv, begend, opt)
int	fun, *nperi, begend, opt;
DEC	*nperd,	*intr, *pv, *pmt, *fv;
{
	int	intper,	ad;
	DEC	dopi, *opi=&dopi, dfractper, *fractper=&dfractper;
	DEC	dmess, *mess=&dmess, dalpha, *alpha=&dalpha;
	DEC	dpva, *pva=&dpva, dfvopi, *fvopi=&dfvopi;
	DEC	dpmtm, *pmtm=&dpmtm, dopitmn, *opitmn=&dopitmn;
	DEC	dtemp, *temp=&dtemp, dtemp2, *temp2=&dtemp2;
	DEC	dnintr,	*nintr=&dnintr;
	DEC	ddad, *dad=&ddad, dopitmna, *opitmna=&dopitmna;


	if (!intr||!pv||!pmt||!fv||!nperd)  {
		_MacErr(GM_NULLPOINTER);
		return(GM_NULL);
	}
	if ((opt!=GM_I && _MacBad(intr)) || (opt!=GM_PV	&& _MacBad(pv))	||
	    (opt!=GM_PMT && _MacBad(pmt)) || (opt!=GM_FV && _MacBad(fv))) {
		    _MacErr(GM_INIT);
		    return(GM_NULL);
	    }

	if ((begend!=GM_BEGIN&&begend!=GM_END&&fun!=0)||
		(opt!=GM_N&&opt!=GM_I&&opt!=GM_PV&&opt!=GM_PMT&&opt!=GM_FV)||
		(opt!=GM_I&&CompareDecimal(intr,&decMinusHundred)!=1))	{
		_MacErr(GM_ARGVAL);
		return(GM_NULL);
	}

	intper = 0;
	ad = begend;

	if (opt!=GM_N)	{
/* determine integer & fractional parts	of nper, if in range */
		if (fun<=1)  {
			if (*nperi<0)  {
				_MacErr(GM_ARGVAL);
				return(GM_NULL);
			}
			intper=*nperi;
		}
		else {
			if (_MacIsDecN(nperd)||
				(CompareDecimal(nperd,&decMaxTime)==1))	{
				_MacErr(GM_ARGVAL);
				return(GM_NULL);
			}
			(void) _TruncateDec80Bit(temp, nperd, 0);
			intper=temp->dc.sl[0];
			(void) _SubDec80Bit(fractper, nperd, temp);
		}
	}

	if (opt==GM_I)	{
		opi=_InterestAux(fun, intper, fractper,
			intr, pv, pmt, fv, begend);
		return(opi);
	}

	_MacDCopy(nintr, intr);
	nintr->dc.id+=2;

/* opi = 1+i */
	(void) _AddDec80Bit(opi, nintr,	&decOne);

	if (opt!=GM_N)	{
/* handle zero interest	*/
		if (_MacIsDecZ(intr))  {
			(void) ConvLongToDecimal(temp2,	(long) intper);

			if (opt==GM_PV)	 {
				(void) _MulDec80Bit(temp, temp2, pmt);
				(void) _AddDec80Bit(temp, temp,	fv);
				_MacDChgs(temp);
				(void) _ScaleDec80Bit(pv, temp,	2);
				return(pv);
			}

			if (opt==GM_FV)	 {
				(void) _MulDec80Bit(temp, temp2, pmt);
				(void) _AddDec80Bit(temp, temp,	pv);
				_MacDChgs(temp);
				(void) _ScaleDec80Bit(fv, temp,	2);
				return(fv);
			}

			if (opt==GM_PMT)  {
				if (intper==0)	{
					_MacErr(GM_ARGVAL);
					return(GM_NULL);
				}
				(void) _AddDec80Bit(temp, pv, fv);
				_MacDChgs(temp);
				(void) _DivRndDec80Bit(pmt, temp, temp2, 2);
				return(pmt);
			}
		}

/* calculate alpha */
		if (fun==2)  {
			(void) _MulDec80Bit(temp, nintr, fractper);
			(void) _AddDec80Bit(alpha, temp, &decOne);
		}
		if (fun==3)  {
			(void) _LnDec80Bit(temp, opi);
			(void) _MulDec80Bit(temp, temp,	fractper);
			(void) _ExpDec80Bit(alpha, temp);
		}

/* handle zero nper */
		if (intper==0)	{
			if (opt==GM_PMT)  {
				_MacErr(GM_ARGVAL);
				return(GM_NULL);
			}

			if (opt==GM_PV)	 {
				if (fun<=1)
					(void) _ScaleDec80Bit(pv, fv, 2);
				else
					(void) _DivRndDec80Bit(
						pv, fv,	alpha, 2);
				_MacDChgs(pv);
				return(pv);
			}

			if (opt==GM_FV)	 {
				if (fun<=1)
					(void) _ScaleDec80Bit(fv, pv, 2);
				else  {
					(void) _MulDec80Bit(fv,	pv, alpha);
					(void) _ScaleDec80Bit(fv, fv, 2);
				}
				_MacDChgs(fv);
				return(fv);
			}
		}

/* calcuate (1+i)^-n and (1+iS)[1-(1+i)^-n]/i */
		(void) _IntPwrDec80Bit(opitmn, opi, -intper);
		(void) _SubDec80Bit(temp, &decOne, opitmn);

/* Advance payment needs (1+i)^-(n-a) */
		if (fun==0)  {
			(void) _IntPwrDec80Bit(opitmna,	opi, -(intper-ad));
			(void) _SubDec80Bit(temp, &decOne, opitmna);
			(void) ConvLongToDecimal(dad, (long) ad);
		}
		else
		(void) _SubDec80Bit(temp, &decOne, opitmn);

		(void) _DivDec80Bit(mess, temp,	nintr);
		if (begend==GM_BEGIN &&	fun!=0)
			(void) _MulDec80Bit(mess, mess,	opi);

		if ((opt==GM_PMT||opt==GM_FV))	{
			if (fun>1)
				(void) _MulDec80Bit(pva, pv, alpha);
			if (fun<=1)
				_MacDCopy(pva, pv);
		}

		/* calculate FV*(1+i)^-n */
		if (opt==GM_PV||opt==GM_PMT)
			(void) _MulDec80Bit(fvopi, fv, opitmn);

		if (opt==GM_PV||opt==GM_FV)
			(void) _MulDec80Bit(pmtm, pmt, mess);

		if (opt==GM_PV)	 {
			(void) _AddDec80Bit(temp, pmtm,	fvopi);
			_MacDChgs(temp);
			if (fun>1)
				(void) _DivRndDec80Bit(pv, temp, alpha,	2);
			if (fun==1)
				(void) _ScaleDec80Bit(pv, temp,	2);
			if (fun==0)  {
				(void) _MulDec80Bit(temp2, dad,	pmt);
				(void) _SubDec80Bit(temp, temp,	temp2);
				(void) _ScaleDec80Bit(pv, temp,	2);
			}
			return(pv);
		}

		if (opt==GM_PMT)  {
			(void) _AddDec80Bit(temp, pva, fvopi);
			_MacDChgs(temp);
			if (fun==0)
				(void) _AddDec80Bit(mess, mess,	dad);
			(void) _DivRndDec80Bit(pmt, temp, mess,	2);
			return(pmt);
		}

		if (opt==GM_FV)	 {
			if (fun==0)  {
				(void) _MulDec80Bit(temp, pmt, dad);
				(void) _AddDec80Bit(pva, pv, temp);
			}
			(void) _AddDec80Bit(temp, pva, pmtm);
			_MacDChgs(temp);
			(void) _DivRndDec80Bit(fv, temp, opitmn, 2);
			return(fv);
		}
	}

	/* opt==GM_N */
/* handle zero interest	*/
	if (_MacIsDecZ(intr))  {
		if (_MacIsDecZ(pmt))  {
			_MacErr(GM_ARGVAL);
			return(GM_NULL);
		}

		(void) _AddDec80Bit(temp, pv, fv);
		_MacDChgs(temp);

		(void) _DivTrnDec80Bit(temp2, temp, pmt, 0);
/* check for exact division => don't increment */
		(void) _MulDec80Bit(mess, temp2, pmt);
		if (CompareDecimal(temp,mess)!=0)
			(void) _AddDec80Bit(temp2, temp2, &decOne);

		if (_MacIsDecN(temp2)||CompareDecimal(temp2,&decMaxTime)==1) {
			_MacErr(GM_ARGVAL);
			return(GM_NULL);
		}

		if (fun==1)  {
			*nperi=temp2->dc.sl[0];
			return(GM_NULL);
		}
		else  {
			_MacDCopy(nperd, temp2);
			return(nperd);
		}
	}

/* first solve for integer part	of period */
	(void) _DivDec80Bit(temp, pmt, nintr);
	if (begend==GM_BEGIN)
		(void) _MulDec80Bit(temp, temp,	opi);
	_MacDCopy(temp2, temp);
	(void) _AddDec80Bit(temp, temp,	pv);
	(void) _SubDec80Bit(temp2, temp2, fv);
	if (_MacIsDecZ(temp)||_MacIsDecZ(temp2))  {
		_MacErr(GM_ARGVAL);
		return(GM_NULL);
	}
	(void) _DivDec80Bit(temp, temp2, temp);
	if (!_MacIsDecP(temp))	{
		_MacErr(GM_ARGVAL);
		return(GM_NULL);
	}
	(void) _LnDec80Bit(temp, temp);
	(void) _LnDec80Bit(temp2, opi);

	(void) _DivTrnDec80Bit(temp, temp, temp2, 0);
	if (_MacIsDecN(temp)||(CompareDecimal(temp,&decMaxTime)==1)) {
		_MacErr(GM_ARGVAL);
		return(GM_NULL);
	}

	intper=temp->dc.sl[0]+1;
	if (intper==1)
		intper--;

/* the +1 rounding assumes the ratio of	logarithms is not an integer */
	if (fun==1)  {
		*nperi=intper;	/* always round	up */
		return(GM_NULL);
	}

	/* fun > 1 */
	(void) ConvLongToDecimal(nperd,	(long) intper);
/* zero	pv makes fractional period irrelevant */
	if (_MacIsDecZ(pv))
		return(nperd);
/* now solve for fractional part & add to integer part */
	(void) _IntPwrDec80Bit(opitmn, opi, -intper);
	(void) _SubDec80Bit(temp, &decOne, opitmn);
	(void) _DivDec80Bit(mess, temp,	nintr);
	if (begend==GM_BEGIN)
		(void) _MulDec80Bit(mess, mess,	opi);
	(void) _MulDec80Bit(pmtm, pmt, mess);
	(void) _MulDec80Bit(fvopi, fv, opitmn);
	(void) _AddDec80Bit(temp, pmtm,	fvopi);
	_MacDChgs(temp);
	(void) _DivDec80Bit(alpha, temp, pv);
	if (fun==2)  {
		(void) _SubDec80Bit(temp, alpha, &decOne);
		(void) _DivDec80Bit(fractper, temp, nintr);
		(void) _AddDec80Bit(nperd, nperd, fractper);
		(void) _Sq5UnsTo4Uns(nperd);
	}
	if (fun==3)  {
		if (!_MacIsDecP(alpha))
			return(nperd);
		(void) _LnDec80Bit(temp, alpha);
		(void) _LnDec80Bit(temp2, opi);
		(void) _DivDec80Bit(fractper, temp, temp2);
		(void) _AddDec80Bit(nperd, nperd, fractper);
		(void) _Sq5UnsTo4Uns(nperd);
	}
	return(nperd);
}