/************************************************************************/
/*									*/
/* am - Amortization Schedule Generator					*/
/*									*/
/* (c) Copyright 1987, 1992, 1993	Brett K. Carver			*/
/*									*/
/************************************************************************/
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "patchlevel.h"

/* these two constants may be changed at will */
#define ARRAY_SIZE 128		/* special arrays size: -I, -E, -U	*/
#define SEP '-'			/* seperator for dates: 1/1/93 or 1-1-93*/

/* these constants should not be changed */
#define FIELD_LENGTH 15		/* how wide are numbers: 123,456,789.12	*/
#define MAX_VAL 999999999.99	/* maximum dollar amount allowed	*/
#define S_LEN 100		/* length of input buffer string	*/
#define PLURAL(x) (x)!=1?"s":""	/* adds an 's' for plural items		*/

#ifndef TRUE
# define TRUE 1
# define FALSE 0
#endif

int	number;			/* number of payments			*/
double	amount;			/* amount of loan			*/
double	rate;			/* interest rate			*/
double	payment;		/* amount of monthly payment		*/
int	month, day, year;	/* starting date			*/
int	e_idx = 0;		/* index into extra payment arrays	*/
int	e_mon[ARRAY_SIZE];	/* months of extra payments		*/
double	e_pay[ARRAY_SIZE];	/* amounts of extra payments		*/
int	u_idx = 0;		/* index into unusual payment arrays	*/
int	u_mon[ARRAY_SIZE];	/* months of unusual payments		*/
double	u_pay[ARRAY_SIZE];	/* amounts of unusual payments		*/
int	i_idx = 0;		/* index into interest rate arrays	*/
int	i_mon[ARRAY_SIZE];	/* months of interest rates		*/
double	i_pay[ARRAY_SIZE];	/* amounts of interest rates		*/
int	list;			/* months to list for			*/
int	delta_day;		/* first month 30 +/- delta days	*/
int	balloon = FALSE;	/* last payment is a balloon payment	*/
int	format = FALSE;		/* include nroff formatting commands	*/
char	istr[S_LEN];		/* input buffer string			*/

#define	def_number	360	/* default number of payments		*/
#define	def_amount	10000.0	/* default amount of loan		*/
#define	def_rate	10.0	/* default interest rate		*/
#define	def_payment	100.0	/* default amount of monthly payment	*/
#define def_start	"1-1-90"/* default starting date		*/
#define	def_list	0	/* default months to list for		*/

char * usage_text[] = {
	"\t-n\tnumber of payments (-n-30 is 30 years, 360 months):",
	"\t\t\t=0 - till paid in full",
	"\t\t\t>0 - number of months",
	"\t\t\t<0 - number of years",
	"\t-a\tamount of loan (<= $999,999,999.99)",
	"\t-i\tinterest rate (-i10 is 10%)",
	"\t-p\tmonthly payment amount:",
	"\t\t\t=0 - computed based on number of payments",
#if (SEP == '-')
	"\t-s\tstart first payment on mm-dd-yy (-s12-15-93 is Dec 15 1993)",
#else
	"\t-s\tstart first payment on mm/dd/yy (-s12/15/93 is Dec 15 1993)",
#endif
	"\t-l\tlist loan for:",
	"\t\t\t=0 - list all payments",
	"\t\t\t>0 - number of months",
	"\t\t\t<0 - number of years",
	"\t-b\tmake last payment a balloon payment if ballance due",
	"\t-I\tmonth:rate for new interest rate[s] (up to 128 allowed):",
	"\t\t\t>0 - new interest rate (-e8:12 rate at 12% on month 8)",
	"\t-E\tmonth:amount for extra payment[s] (up to 128 allowed):",
	"\t\t\t>0 - extra principal payment amount (-e7:50 extra $50)",
	"\t\t\t<0 - add to unpaid principal (-e5:-100 add $100)",
	"\t-U\tmonth:unusual payment[s] (up to 128 allowed):",
	"\t\t\t=0 - missed payment (-u9:0 missed payment on month 9)",
	"\t\t\t>0 - non-standard payment amount (-u7:75 only paid $75)",
	"\t-D\tchange first month by +/- days",
	"\t\t\t>0 - month had more days than normal",
	"\t\t\t<0 - month had less days than normal",
	"\t-f\tOutput with nroff formatting commands",
	0 };					/* last entry MUST be null */

char *titles = "NUMBER   DATE           PAYMENT        INTEREST       PRINCIPAL         BALANCE";

/************************************************************************/
/*									*/
/************************************************************************/
char * add_commas(value)
double value;
{
static char string[32];
static char * sptr = &string[0];
register int i, j, k;
register int length;
int negative;

negative = FALSE;
if (value < 0) {
	value = -value;
	negative = TRUE;
	}

if (value > MAX_VAL) {
	fprintf(stderr, "Error:\ta value has exceeded $999,999,999.99,\n");
	fprintf(stderr, "\tplease use another program for calculating the national debt.\n");
	exit(1);
	}

sprintf(sptr, "%.2f                ", value);
length = strlen(sptr);	/* sprintf() doesn't return length on all unix's */
length -= 16;		/* 1234567890123456 */
length --;		/* make 0 relative  */
string[FIELD_LENGTH] = '\000';
j = FIELD_LENGTH - 1;
k = 6;

for (i=length; i>=0; i--) {
	if ((k--) == 0) {
		string[j--] = ',';
		k = 2;
		}
	string[j--] = string[i];
	string[i] = ' ';
	}

if (negative)
	string[j--] = '-';

return(sptr);
}

/************************************************************************/
/*									*/
/************************************************************************/
int get_int(text, def)
char *text;
int def;
{
int value;

fprintf(stderr, "%s [%d]: ", text, def);
fgets(istr, S_LEN, stdin);

if (strlen(istr) <= 1)
	return(def);

value = 0;	/* in case nothing scans */
sscanf(istr, "%d", &value);

return(value);
}

/************************************************************************/
/*									*/
/************************************************************************/
double get_double(text, def)
char *text;
double def;
{
double value;

fprintf(stderr, "%s [%.0f]: ", text, def);
fgets(istr, S_LEN, stdin);

if (strlen(istr) <= 1)
	return(def);

value = 0.0;	/* in case nothing scans */
sscanf(istr, "%lf", &value);

return(value);
}

/************************************************************************/
/*									*/
/************************************************************************/
dup_warning(param)
int param;
{
fprintf(stderr, "Warning: duplicate '-%c' parameter\n", param);
}

/************************************************************************/
/*									*/
/************************************************************************/
init(argc, argv)
int argc;
char *argv[];
{
extern char *optarg;
extern int optind;
int c;
int error_flag	= FALSE;
char z[2];			/* a sscanf temp */
int got_number	= FALSE;
int got_amount	= FALSE;
int got_rate	= FALSE;
int got_payment	= FALSE;
int got_start	= FALSE;
int got_list	= FALSE;
int i;

while ((c = getopt(argc, argv, "n:a:i:p:s:l:bI:E:U:D:f?")) != EOF)
	switch (c) {

		case 'n':
			if (got_number)
				dup_warning(c);
			sscanf(optarg, "%d", &number);
			got_number = TRUE;
			break;

		case 'a':
			if (got_amount)
				dup_warning(c);
			sscanf(optarg, "%lf", &amount);
			if (amount <= 0.0) {
				fprintf(stderr, "Error: amount <= 0.0\n");
				error_flag = TRUE;
				}
			if (amount > MAX_VAL) {
				fprintf(stderr,
					"Error: amount > 999,999,999.99\n");
				error_flag = TRUE;
				}
			got_amount = TRUE;
			break;

		case 'i':
			if (got_rate)
				dup_warning(c);
			sscanf(optarg, "%lf", &rate);
			if (rate <= 0.0) {
				fprintf(stderr, "Error: rate <= 0.0\n");
				error_flag = TRUE;
				}
			got_rate = TRUE;
			break;

		case 'p':
			if (got_payment)
				dup_warning(c);
			sscanf(optarg, "%lf", &payment);
			if (payment < 0.0) {
				fprintf(stderr, "Error: payment < 0.0\n");
				error_flag = TRUE;
				}
			got_payment = TRUE;
			break;

		case 's':
			if (got_start)
				dup_warning(c);
			sscanf(optarg, "%d%[- /]%d%[- /]%d",
						&month, z, &day, z, &year);
			got_start = TRUE;
			break;

		case 'l':
			if (got_list)
				dup_warning(c);
			sscanf(optarg, "%d", &list);
			got_list = TRUE;
			break;

		case 'b':
			if (balloon)
				dup_warning(c);
			balloon = TRUE;
			break;

		case 'E':
			if (e_idx >= ARRAY_SIZE) {
				fprintf(stderr, "Error: more than %d extra payments\n",
					ARRAY_SIZE);
				error_flag = TRUE;
				break;
				}
			sscanf(optarg, "%d:%lf", &e_mon[e_idx], &e_pay[e_idx]);
			if (e_mon[e_idx] <= 0) {
				fprintf(stderr,
					"Error: invalid extra payment month: %d\n",
					e_mon[e_idx]);
				error_flag = TRUE;
				}
			if (e_pay[e_idx] == 0) {
				fprintf(stderr,
					"Warning: $0.00 extra payment for month: %d, ignored\n",
					e_mon[e_idx]);
				}
			for (i=0; i<e_idx; i++)
				if (e_mon[i] == e_mon[e_idx]) {
					fprintf(stderr,
						"Error: duplicate extra payment month: %d\n",
						e_mon[e_idx]);
					error_flag = TRUE;
					}
			e_idx++;
			break;

		case 'U':
			if (u_idx >= ARRAY_SIZE) {
				fprintf(stderr, "Error: more than %d unusual payments\n",
					ARRAY_SIZE);
				error_flag = TRUE;
				break;
				}
			sscanf(optarg, "%d:%lf", &u_mon[u_idx], &u_pay[u_idx]);
			if (u_mon[u_idx] <= 0) {
				fprintf(stderr,
					"Error: invalid unusual payment month: %d\n",
					u_mon[u_idx]);
				error_flag = TRUE;
				}
			if (u_pay[u_idx] < 0) {
				fprintf(stderr,
					"Error: invalid unusual payment: %d:%.2f\n",
					u_mon[u_idx], u_pay[u_idx]);
				error_flag = TRUE;
				}
			for (i=0; i<u_idx; i++)
				if (u_mon[i] == u_mon[u_idx]) {
					fprintf(stderr,
						"Error: duplicate unusual payment month: %d\n",
						u_mon[u_idx]);
					error_flag = TRUE;
					}
			u_idx++;
			break;

		case 'I':
			if (i_idx >= ARRAY_SIZE) {
				fprintf(stderr, "Error: more than %d interest rate changes\n",
					ARRAY_SIZE);
				error_flag = TRUE;
				break;
				}
			sscanf(optarg, "%d:%lf", &i_mon[i_idx], &i_pay[i_idx]);
			i_pay[i_idx] /= 100.0;
			i_pay[i_idx] /= 12.0;
			if (i_mon[i_idx] <= 0) {
				fprintf(stderr,
					"Error: invalid interest rate change month: %d\n",
					i_mon[i_idx]);
				error_flag = TRUE;
				}
			if (i_pay[i_idx] <= 0) {
				fprintf(stderr,
					"Error: invalid interest rate change: %d:%.2f\n",
					i_mon[i_idx], i_pay[i_idx]);
				error_flag = TRUE;
				}
			for (i=0; i<i_idx; i++)
				if (i_mon[i] == i_mon[i_idx]) {
					fprintf(stderr,
						"Error: duplicate interest rate change month: %d\n",
						i_mon[i_idx]);
					error_flag = TRUE;
					}
			i_idx++;
			break;

		case 'D':
			sscanf(optarg, "%d", &delta_day);
			if ((delta_day < -30) || (delta_day > 30)) {
				fprintf(stderr, "Error: first month can only be +/- 30 days\n");
				error_flag = TRUE;
				}
			break;

		case 'f':
			if (format)
				dup_warning(c);
			format = TRUE;
			break;

		case '?':
			error_flag = TRUE;
			break;

		default:
			error_flag = TRUE;
			break;

		}

if (error_flag || optind < argc) {
	char **text;

	fprintf(stderr, "\n%s: %s\n", argv[0], &ident[5]);
	fprintf(stderr, "\t(c) 1987, 1992, 1993 - Brett K. Carver\n\n");
	fprintf(stderr, "usage: %s [arguments]\n", argv[0]);
	text = usage_text;
	while (*text)
		fprintf(stderr, "%s\n", *text++);
	exit(1);
	}

if (!got_number)
	number = get_int("Enter number of payments (- = years, + = months, 0 = till paid)", def_number);

if (number < 0)
	number *= -12;

if (!got_amount)
	amount = get_double("Enter amount of loan", def_amount);

if (amount <= 0.0) {
	fprintf(stderr, "Error: amount <= 0.0\n");
	exit(1);
	}

if (amount > MAX_VAL) {
	fprintf(stderr, "Error: amount > 999,999,999.99\n");
	exit(1);
	}

if (!got_rate)
	rate = get_double("Enter interest rate(%)", def_rate);

if (rate <= 0.0) {
	fprintf(stderr, "Error: rate <= 0.0\n");
	exit(1);
	}

rate /= 100.0;
rate /= 12.0;

if (!got_payment) {
	if (number == 0)
		payment = get_double("Enter amount of payment (required)",
					def_payment);
	else
		payment = get_double("Enter amount of payment (0 for computed)",
					0.0);
	}

if (payment < 0.0) {
	fprintf(stderr, "Error: payment < 0.0\n");
	exit(1);
	}

while ((payment < 0.01) && (number == 0)) {
	fprintf(stderr, "Error: payment required (unable to compute):");
	scanf("%lf", &payment);
	}

if (payment < 0.01) {
	/****************************************/
	/*		     A  *  R		*/
	/*		----------------	*/
	/*	P  =	         1		*/
	/*		 1 - ----------		*/
	/*		             N		*/
	/*		      (1 + R)		*/
	/****************************************/
	payment = amount * rate /
		(1.0 - (1.0 /
			pow(1.0 + rate, (double)number)));
	}

payment = ceil(payment * 100.0) / 100.0;

if (payment < rate * amount) {
	fprintf(stderr, "Warning: payment < interest amount.\n");
	}

if (!got_start) {
	fprintf(stderr, "Starting month%cday%cyear [1%c1%c90]: ",
		SEP, SEP, SEP, SEP);
	fgets(istr, S_LEN, stdin);
	if (strlen(istr) <= 1)
		strcpy(istr, def_start);
	sscanf(istr, "%d%[- /]%d%[- /]%d", &month, z, &day, z, &year);
	}

year %= 100;

if (!got_list)
	list = get_int("Number of payments to list (0 for all)", def_list);

if (list < 0)
	list *= -12;

}

/************************************************************************/
/*									*/
/************************************************************************/
print_header()
{

if (format) {
	printf(".nf\n");			/* no fill		*/
	printf(".na\n");			/* no adjust		*/
	printf(".ll 80\n");			/* line length		*/
	printf(".lt 80\n");			/* title length		*/
	printf(".de hd\n");			/* define header	*/
	printf(".sp 4\n");
	printf(".tl '\\fI%s\\fR'\n", titles);
	printf("..\n");
	printf(".de fo\n");			/* define footer	*/
	printf(".sp 2\n");
	printf(".tl ''- %% -''\n");
	printf(".bp\n");
	printf("..\n");
	printf(".wh 0 hd\n");			/* header trap		*/
	printf(".wh -4 fo\n");			/* footer trap		*/
	printf(".sp 3\n");
	}

if (number == 0)
	printf("Number of payments:   till paid\n");
else
	printf("Number of payments:       %5d\n", number);

printf("Amount of loan: %s\n", add_commas(amount));
printf("Interest rate:       %8.2f %%\n", rate*100.0*12.0);
printf("Payment amount: %s\n", add_commas(payment));
printf("Starting date:         %.2d%c%.2d%c%.2d\n", month, SEP, day, SEP, year);

if (e_idx > 0)
	printf("With %d extra payment%s defined.\n", e_idx, PLURAL(e_idx));

if (u_idx > 0)
	printf("With %d unusual payment%s defined.\n", u_idx, PLURAL(u_idx));

if (i_idx > 0)
	printf("With %d interest rage change%s defined.\n", i_idx,
		PLURAL(i_idx));

if (delta_day != 0)
	printf("With a first month of %d day%s.\n", 30+delta_day,
		PLURAL(30+delta_day));

if (list != 0) {
	if (list < number || number == 0)
		number = list;
	printf("Listing for %d payment%s, %.1f year%s.\n", number,
		PLURAL(number), number/12.0, PLURAL(number/12.0));
	}
else	{
	if (number != 0)
		printf("Listing for %d payment%s, %.1f year%s.\n", number,
			PLURAL(number), number/12.0, PLURAL(number/12.0));
	else	{
		printf("Listing till paid.\n");
		number = 99999;
		}
	}

if (balloon)
	printf("With a balloon final payment.\n");

if (format) {
	printf(".sp 3\n");
	printf("\\fI%s\\fR\n", titles);
	}
else {
	printf("\n\n\n");
	printf("%s\n", titles);
	}
}

/************************************************************************/
/*									*/
/************************************************************************/
print_totals(leader, t_pay, t_int, t_pcp)
char *leader;
double t_pay, t_int, t_pcp;
{

printf("%s", leader);

if (format)
	printf("\\fB");

printf("%s ", add_commas(t_pay));
printf("%s ", add_commas(t_int));
printf("%s",  add_commas(t_pcp));

if (format)
	printf("\\fR");

printf("\n");
}

/************************************************************************/
/*									*/
/************************************************************************/
main(argc, argv)
int argc;
char *argv[];
{
double interest;
double principal;
double pay;
double t_interest	= 0.0;
double t_principal	= 0.0;
double t_payment	= 0.0;
double gt_interest	= 0.0;
double gt_principal	= 0.0;
double gt_payment	= 0.0;
int i, j;

init(argc, argv);
print_header();

for (i=1; i<=number; i++) {

	for (j=0; j<i_idx; j++)
		if (i == i_mon[j]) {		/* interest rate change */
			rate = i_pay[j];
			break;			/* found one, we're done */
			}

	interest = rate * amount;

	if (delta_day != 0)
		if (i == 1)
			/* adjust first month to be days more/less than 30 */
			interest += delta_day * (rate / 30.0) * amount;

	/* INTEREST */
	/****************************************************************/
	/* There are several ways one might round the interest due.	*/
	/* Un-comment a different method if that applies in your case.	*/
	/****************************************************************/
	/* round up */
	/* interest = ceil(interest * 100.0) / 100.0; */
	/* normal rounding ( >= .5 < )*/
	interest = floor(interest * 100.0 + 0.5) / 100.0;
	/* round down (truncate) */
	/* interest = floor(interest * 100.0) / 100.0; */

	principal = payment - interest;
	pay = payment;

	for (j=0; j<u_idx; j++)
		if (i == u_mon[j]) {		/* unusual payment defined */
			if (u_pay[j] == 0) {		/* missed payment */
				principal = -interest;
				pay = interest = 0.0;
				}
			else {				/* low payment */
				pay = u_pay[j];
				if (interest < pay)
					principal = pay - interest;
				else {
					principal = -(interest - pay);
					interest = pay;
					}
				}
			break;			/* found one, we're done */
			}

	for (j=0; j<e_idx; j++)
		if (i == e_mon[j]) {		/* extra payment defined */
			if (e_pay[j] < 0) {		/* penality */
				principal += e_pay[j];
				pay += e_pay[j];
				}
			else {				/* extra principal */
				principal += e_pay[j];
				pay += e_pay[j];
				}
			break;			/* found one, we're done */
			}

	/* SPECIAL */
	/********************************************************/
	/* This is the place to do any special case handling	*/
	/* that can't be handled by the -e, -u, or -d arguments	*/
	/* It is triggered by a specific payment number (to be	*/
	/* filled in at the 'xx') and allows for modifying the	*/
	/* principal or payment amound for that month.		*/
	/*		>>>>>> USE WITH CARE <<<<<<		*/
	/********************************************************/
/*	if (i == xx) {
		principal += <some value>;
		pay	  += <some value>;
		}					*/

	if (principal >= amount) {		/* loan paid off */
		principal = amount;
		pay = interest + principal;
		number = 0;
		}

	if (i == number && balloon) {		/* loan done, balloon */
		principal = amount;
		pay = interest + principal;
		number = 0;
		}

	amount -= principal;
	printf("%4d   %.2d%c%.2d%c%.2d ", i, month, SEP, day, SEP, year);
	printf("%s ", add_commas(pay));
	printf("%s ", add_commas(interest));
	printf("%s ", add_commas(principal));
	printf("%s\n", add_commas(amount));
	t_interest += interest;

	if (principal > 0)
		t_principal += principal;

	if (pay > 0)
		t_payment += pay;

	if (month == 12) {
		print_totals("                ", t_payment, t_interest,
				t_principal);
		gt_interest += t_interest;
		gt_principal += t_principal;
		gt_payment += t_payment;
		t_interest = t_principal = t_payment = 0.0;
		month = 0;
		year += 1;

		if (year > 99)
			year = 0;

		if (i < number) {
			printf("\n");
			if (format)
				printf(".ne 14\n"); /* need 14 lines per year */
			else
				printf("%s\n", titles);
			}
		}
	month += 1;
	}

if (month != 1) {				/* pickup a partial year */
	print_totals("                ", t_payment, t_interest, t_principal);
	gt_interest += t_interest;
	gt_principal += t_principal;
	gt_payment += t_payment;
	}

printf("\n");
print_totals("loan totals:    ", gt_payment, gt_interest, gt_principal);

exit(0);
}

/* end of file */
