/*	Copyright (C) 1990 Free Software Foundation, Inc.

This file is part of Oleo, the GNU Spreadsheet.

Oleo is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.

Oleo is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Oleo; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include "funcdef.h"
#include "sysdef.h"

#include "global.h"
#include "cell.h"
#include "eval.h"
#include "errors.h"

struct value {
	int	type;
	union vals x;
};

#define Float	x.c_d
#define String	x.c_s
#define Int	x.c_l
#define Value	x.c_i
#define Rng	x.c_r

static double
pmt FUN3(double,principal, double,rate, double,term)
{
	return (principal * rate)/ ( 1 - pow(1+rate,-(term)));
}


static int
npv FUN3(struct rng *,rng, double,rate, double *,putres)
{
	double npv;
	int i;
	double f;
	CELL *cell_ptr;
	char *strptr;

	find_cells_in_range(rng);
	for(i=1,npv=0.0;cell_ptr=next_cell_in_range();i++) {
		switch(GET_TYP(cell_ptr)) {
		case 0:
			f=0.0;
			goto know_f;

		case TYP_INT:
			f=(double)(cell_ptr->cell_int);
			goto know_f;

		case TYP_FLT:
			f=cell_ptr->cell_flt;
			goto know_f;

		case TYP_STR:
			strptr=cell_ptr->cell_str;
			f=astof(&strptr);
			if(*strptr)
				return NON_NUMBER;
		know_f:
			npv+=f * (1.0/(pow(1.0+rate,(double)i)));
			break;

		case TYP_ERR:
			return cell_ptr->cell_err;

		default:
			return NON_NUMBER;
		}
	}

	*putres = npv;
	return 0;
}

static void
do_pmt FUN1(struct value *,p)
{
	p->Float=pmt(p->Float,(p+1)->Float,(p+2)->Float);
}

static void
do_pv FUN1(struct value *,p)
{
	double	payment,
		interest,
		term;

	payment=p[0].Float;
	interest=p[1].Float;
	term=p[2].Float;

	p->Float=payment * ((1 - pow(1+interest,-term))/ interest);
}

static void
do_npv FUN1(struct value *,p)
{
	int tmp;

	tmp=npv(&(p->Rng),(p+1)->Float,&(p->Float));
	if(tmp) {
		p->Value=tmp;
		p->type=TYP_ERR;
	}
	p->type=TYP_FLT;
}

static void
do_irr FUN1(struct value *,p)
{
	double try;
	double res;
	double mint,maxt;
	double minr,maxr;
	int i;
	int tmp;

	minr=maxr=0;
	mint=maxt=0;
	while(minr>=0) {
		mint+=1;
		tmp=npv(&(p->Rng),mint,&minr);
		if(tmp)	{
			p->Value=tmp;
			p->type=TYP_ERR;
			return;
		}
	}
	while(maxr<=0) {
		maxt-=1;
		tmp=npv(&(p->Rng),maxt,&maxr);
		if(tmp) {
			p->Value=tmp;
			p->type=TYP_ERR;
			return;
		}
	}
	try=(p+1)->Float;
	for(i=0;;i++) {
		if(i==40) {
			p->Value=BAD_INPUT;
			p->type=TYP_ERR;
			return;
		}
		tmp=npv(&(p->Rng),try,&res);
		if(tmp) {
			p->Value=tmp;
			p->type=TYP_ERR;
			return;
		}
		if(fabs(res*1000000.0)<1)
			break;
		if(res>0) {
			maxt=try;
			maxr=res;
		} else if(res<0) {
			mint=try;
			minr=res;
		}
		if(minr/-10>maxr) {
			/* it is quite near maxt */
			try=(maxt*10+mint)/11;
		} else if(minr/-2>maxr) {
			try=(maxt*2+mint)/3;
		} else if(minr*-10<maxr) {
			/* It is quite near mint */
			try=(maxt+mint*10)/11;
		} else if(minr*-2<maxr) {
			try=(maxt+mint*2)/3;
		} else
			try=(maxt+mint)/2;
	}
	p->Float=try;
	p->type=TYP_FLT;
}

static void
do_fv FUN1(struct value *,p)
{
	double payment	= p->Float;
 	double interest = (p+1)->Float;
 	double term	= (p+2)->Float;

	p->Float = payment * ((pow(1+interest,term)-1)/interest);
}

static void
do_rate FUN1(struct value *,p)
{
	double future	= p->Float;
	double present	= (p+1)->Float;
 	double term	= (p+2)->Float;

	p->Float = pow(future/present,1/term)-1;
}

static void
do_term FUN1(struct value *,p)
{
	double payment	= p->Float;
	double interest	= (p+1)->Float;
	double future	= (p+2)->Float;

	p->Float = log(1+future*(interest/payment))/log(1+interest);
}

static void
do_cterm FUN1(struct value *,p)
{
	double interest	= (p)->Float;
	double future	= (p+1)->Float;
	double present	= (p+2)->Float;

	p->Float=log(future/present)/log(1+interest);
}

static void
do_sln  FUN1(struct value *,p)
{
	double cost	= (p)->Float;
	double salvage	= (p+1)->Float;
	double life	= (p+2)->Float;

	p->Float=(cost-salvage)/life;
}

static void
do_syd FUN1(struct value *,p)
{
	double cost,salvage,life,period;
			
	cost=p->Float;
	salvage=(p+1)->Float;
	life=(p+2)->Float;
	period=(p+3)->Float;
	
	if(period>life)		/* JF is this right? */
		p->Float=salvage;
	else
		p->Float=((cost-salvage)*(life-period+1))/(life*((life+1)/2));
}


static void
do_ddb FUN1(struct value *,p)
{
	double cost	= (p  )->Float;
	double salvage	= (p+1)->Float;
	long life	= (p+2)->Int;
	long period	= (p+3)->Int;

	double bookval,tmp;
	long n;

	if(period<1 || period>life || life<1) {
		p->Value=OUT_OF_RANGE;
		p->type=TYP_ERR;
		return;
	}
	bookval=cost;
	tmp=0;
	for(n=0;n<period;n++) {
		tmp=(bookval*2)/life;
		bookval-=tmp;
		if(bookval<salvage) {
			tmp+=bookval-salvage;
			bookval=salvage;
		}
	}
	p->Float=tmp;
}

static void
do_anrate FUN1(struct value *,p)
{
	double in_pmt	= (p  )->Float;
	double present	= (p+1)->Float;
	double term	= (p+2)->Float;

	double tr_lo,tr_hi;
	double mytry;
	double try_pmt;
	int n;

	if(in_pmt*term==present) {
		p->Float= 0.0;
		return;
	}
	if(in_pmt*term<present) {
		tr_lo= -1;
		tr_hi=  0;
		while(pmt(present,tr_lo,term)>in_pmt) {
			tr_hi=tr_lo;
			tr_lo*=2;
		}
	} else {
		tr_lo=0;
		tr_hi=1;
		while(pmt(present,tr_hi,term)<in_pmt) {
			tr_lo=tr_hi;
			tr_hi*=2;
		}
	}
	for(n=0;n<40;n++) {
		mytry=(tr_lo+tr_hi)/2;
		try_pmt=pmt(present,mytry,term);
		if(try_pmt<in_pmt)
			tr_lo=mytry;
		else if(try_pmt>in_pmt)
			tr_hi=mytry;
		else
			break;
	}
	p->Float=mytry;
}

static void
do_anterm FUN1(struct value *,p)
{
	double payment	= (p  )->Float;
	double principal= (p+1)->Float;
	double rate	= (p+2)->Float;

	p->Float = (-log( 1 - principal*(rate/payment)))/log(1+rate);
}


static void
do_balance FUN1(struct value *,p)
{
	double principal= (p  )->Float;
	double rate	= (p+1)->Float;
	long term	= (p+2)->Int;
 	long period	= (p+3)->Int;

	double tmp_pmt,int_part;
	long num;

	if(term<period) {
		p->Value= OUT_OF_RANGE;
		p->type=TYP_ERR;
		return;
	}
	tmp_pmt=pmt(principal,rate,(double)term);
	for(num=0;num<period;num++) {
		int_part=rate*principal;
		if(int_part>tmp_pmt) {
			p->Value=OUT_OF_RANGE;
			p->type=TYP_ERR;
			return;
		}
		principal-=tmp_pmt-int_part;
	}
	p->Float=principal;
}

static void
do_paidint FUN1(struct value *,p)
{
	double principal= (p  )->Float;
	double rate	= (p+1)->Float;
	long term	= (p+2)->Int;
	long period	= (p+3)->Int;

	double tmp_pmt, int_part, retval;
	long num;

	if(term<period) {
		p->Value= OUT_OF_RANGE;
		p->type=TYP_ERR;
		return;
	}
	tmp_pmt=pmt(principal,rate,(double)term);
	retval=0;
	for(num=0;num<period;num++) {
		int_part=rate*principal;
		if(int_part>tmp_pmt) {
			p->Value= OUT_OF_RANGE;
			p->type=TYP_ERR;
			return;
		}
		principal-=tmp_pmt-int_part;
		retval+=int_part;
	}
	p->Float=retval;
}

static void
do_kint FUN1(struct value *,p)
{
	double principal=(p  )->Float;
	double rate	=(p+1)->Float;
	long term	=(p+2)->Int;
	long period	=(p+3)->Int;

	double tmp_pmt, int_part = 0;
	long num;

	if(term<period) {
		p->Value= OUT_OF_RANGE;
		p->type=TYP_ERR;
		return;
	}
			
	tmp_pmt=pmt(principal,rate,(double)term);
	for(num=0;num<period;num++) {
		int_part=rate*principal;
		if(int_part>tmp_pmt) {
			p->Value= OUT_OF_RANGE;
			p->type=TYP_ERR;
			return;
		}
		principal-=tmp_pmt-int_part;
	}
	p->Float=int_part;
}

static void
do_kprin FUN1(struct value *,p)
{
	double principal=(p  )->Float;
	double rate	=(p+1)->Float;
	long term	=(p+2)->Int;
	long period	=(p+3)->Int;
	double tmp_pmt, int_part=0;
	long num;

	if(term<period) {
		p->Value= OUT_OF_RANGE;
		p->type=TYP_ERR;
		return;
	}

	tmp_pmt=pmt(principal,rate,(double)term);
	for(num=0;num<period;num++) {
		int_part=rate*principal;
		if(int_part>tmp_pmt) {
			p->Value= OUT_OF_RANGE;
			p->type=TYP_ERR;
			return;
		}
		principal-=tmp_pmt-int_part;
	}
	p->Float=tmp_pmt-int_part;
}

static void
do_compbal FUN1(struct value *,p)
{
	double principal= (p  )->Float;
	double rate	= (p+1)->Float;
	double term	= (p+2)->Float;

	p->Float = principal * pow(1+rate,term);
}

struct function busi_funs[] = {
{ C_FN2,	X_A2,	    "RF",   do_npv,	"npv" },
{ C_FN2,	X_A2,	    "RF",   do_irr,	"irr" },

{ C_FN3,	X_A3,	    "FFF",  do_pmt,	"pmt" },
{ C_FN3,	X_A3,	    "FFF",  do_pv,	"pv" },
{ C_FN3,	X_A3,	    "FFF",  do_fv,	"fv" },
{ C_FN3,	X_A3,	    "FFF",  do_rate,	"rate" },
{ C_FN3,	X_A3,	    "FFF",  do_term,	"term" },
{ C_FN3,	X_A3,	    "FFF",  do_cterm,	"cterm" },
{ C_FN3,	X_A3,	    "FFF",  do_sln,	"sln" },
{ C_FN3,	X_A3,	    "FFF",  do_anrate,	"anrate" },
{ C_FN3,	X_A3,	    "FFF",  do_anterm,	"anterm" },
{ C_FN3,	X_A3,	    "FFF",  do_compbal,	"compbal" },

{ C_FN4,	X_A4,	    "FFFF", do_syd,	"syd" },
{ C_FN4,	X_A4,	    "FFII", do_ddb,	"ddb" },
{ C_FN4,	X_A3,	    "FFII", do_balance,	"balance" },
{ C_FN4,	X_A4,	    "FFII", do_paidint,	"paidint" },
{ C_FN4,	X_A4,	    "FFII", do_kint,	"kint" },
{ C_FN4,	X_A4,	    "FFII", do_kprin,	"kprin" },

{ 0,		0,	    "",	    0,		 0 },
};

