/*
 * This module contains functions to perform various analysis calculations
 * on the portfolio.
 */
#include	<stdio.h>
#include        <string.h>
#include	"struct.h"
#include	"port.h"
#include	"calc.h"

/* =======================================================================
 * Public Function Definitions
 */

/*
 * Calculate the fund table index for a fund's alias
 *
 * Input:	Portfolio
 *			Alias		2 Character fund abbreviation
 *
 * Output:	Index		CL_all if alias was not found
 */
FundId CALC_alias_index ( Account *p, char *alias )
{
  FundId f = 0;
  while ( f < p->fund_no ) {
    if (!strcmp( alias, p->fund[f].alias)) return f;
    f++;
  }
  return CL_all;
}

/*
 * Find the maximum historical value of a fund
 *
 * Input:	Account
 *		FundId
 *
 * Output:	Max $ value for the fund
 */
Price CALC_max ( Account *p, FundId f )
{
  int d;
  Price max = 0.0;
  for ( d = 0; d < p->price_no; d++ )
    if ( p->price[ d * p->fund_no + f ] > max )
	  max = p->price[ d * p->fund_no + f ];

  return max;
}

/*
 * Find the minimum historical value of a fund
 *
 * Input:	Account
 *		FundId
 *
 * Output:	Min $ value for the fund
 */
Price CALC_min ( Account *p, FundId f )
{
  int d;
  Price min = 999.0;
  for ( d = 0; d < p->price_no; d++ )
    if ( p->price[ d * p->fund_no + f ] > 0.0 &&
	     p->price[d * p->fund_no + f ] < min )
	  min = p->price[ d * p->fund_no + f ];

  return min;
}

/*
 * Convert YYYYMMDD or YYMMDD to Julian date
 *
 * Input:	Coded date
 *
 * Output:	Julian date
 */
JDate CALC_julian ( Date date )
{
  int iy, im, id;
  int cent, centy, month, year;
  int temp;
  iy = (date / 10000);
  temp = date - iy * 10000;
  im = temp / 100;
  id = temp % 100;
  if (iy < 100 ) iy +=1900;

  if ( im > 2 ) {
    month = im -3;
	year = iy;
  }
  else {
    month = im +9;
	year = iy -1;
  }
  cent = year/100;
  centy = year - (cent * 100);
  temp = 146097 * cent/4;
  temp = temp + 1461 * centy /4;
  temp = temp + (153 * month + 2) /5;
  temp = temp + id;
  return (JDate) (temp);
}

/*
 * Convert Julian date to YYYYMMDD
 *
 * Input:	Julian date
 *
 * Output:	Coded date
 */
Date CALC_date( JDate julian )
{
  int	y, m, d;

  int tl = 146097;
  int tj;

  tj = julian;
  y = (4 * julian - 1 ) / tl;
  tj = 4 * tj - 1 - tl * y;
  d = tj / 4;
  tj = ( 4 * d + 3 ) / 1461;
  d = 4 * d + 3 -1461 * tj;
  d = ( d + 4 ) /4;
  m = ( 5 * d - 3 ) / 153;
  d = 5 * d -3 -153 * m;
  d = ( d + 5 ) / 5;
  y = 100 * y + tj;
  if ( m < 10 ) m = m + 3;
  else {
    m = m - 9;
	y = y + 1;
  }

  return (Date) y*10000+m*100+d;
}

/*
 * Return the value in array that is nearest target
 *
 * Input:	Array	array of integers
 *			Target	value to search for
 *			Min		minimum index into the array >= 0
 *			Max		maximum index into the array <= arraysize
 *
 * Output:	Index	element within the array that is closest to the target
 */
int CALC_bin_search( int *array, int target, int min, int max )
{
  int mid;

  if ( max <= min ) return min;
  mid = (max-min)/2 + min;
  if ( target < array[mid] ) max = mid-1;
  else if ( target > array[mid] ) min = mid+1;
  else return mid;
  CALC_bin_search( array, target, min, max );
}

/* This function will lookup the price of a fund at a certain date
 * if there was no data for that date, an estimate is made from the
 * nearest two dates.
 *
 * Input:	Portfolio
 *			Alias		2 Character fund abbreviation
 *			Date		Date for which the price is required
 *
 * Output:	$ value of fund at given date
 */
Price CALC_find_price ( Account	*p, FundId f, Date date )
{
  int i;
  /* Drop centuries and millenia if they are given */
  if (date > 1000000 ) date %= 1000000;
  /* Handle special case */
  if ( date < p->price_dates[0] ) i = 0;
  else if ( date > p->price_dates[p->price_no-1] ) i = p->price_no-1;
  /* perform binary search */
  else {
    i = CALC_bin_search ( (int *) p->price_dates, (int) date, 0, p->price_no-1 );

    if ( p->price_dates[i] != date || p->price[i*p->fund_no+f] == 0.0 ) {
	  /* Make an estimate */
      int	i1, i2;
      int	d1, d2, d3;
      Price k1, k2;

	  i1 = i2 = i;
	  if (p->price_dates[i] < date) i2 = i+1;
	  else i1 = i-1;
	  /* Make sure we have a valid price not 0.0 */
	  while (i1 > 0 && p->price[i1*p->fund_no+f] == 0) i1--;
	  while (i2 < p->price_no-1 && p->price[i2*p->fund_no+f] == 0) i2++;

      if (i1 == 0.0 ) i1 = i2;
      if (i2 == 0.0 ) i2 = i1;

	  d1 = CALC_julian(p->price_dates[i1]);
	  d2 = CALC_julian(date);
	  d3 = CALC_julian(p->price_dates[i2]);
	  k1 = (float)(d2-d1)/(float)(d3-d1);
	  k2 = 1.0 - k1;
	  return(Price)( p->price[i1*p->fund_no+f]*k2
	               + p->price[i2*p->fund_no+f]*k1 );
	}
  }
  return (Price) p->price[i*p->fund_no + f];
}


/* This function will determine the average price for a fund on a
 * given date within a given date range.
 *
 * Input:	Portfolio
 *			Alias		2 Character fund abbreviation
 *			Date		Date for which the price is required
 *			Range		number of days back to include in avg
 *
 * Output:	avg $ value of fund at given date
 */
Price CALC_avg_price ( Account *p, FundId f, Date date, int range )
{
  Price avg_price = 0.0;
  JDate j;
  int i;
  i = range;
  j = CALC_julian ( date );
  for (i=0; i < range; i++)
    avg_price += CALC_find_price( p, f, CALC_date(j - i));
  return (Price) (avg_price / (Price) range);
}

/*
 * This function processes all the transactions upto the given date
 * and computes:
 * 1. The total units owned
 * 2. The current value of the fund
 * 3. The average unit cost
 * 4. The dollars * days invested in the fund to date
 *
 * Input:	Portfolio
 *			Date		0		implies most recent date
 *			Fund		CL_all	implies all funds
 *
 * Output:	-
 */
void CALC_totals ( Account *p, Date date, FundId fund )
{
  int	t;
  FundId	sf, ef, f;
  int	cd;
  Price	pr;

  sf = 0; ef = p->fund_no-1;

  if ( date == 0 ) cd = CALC_julian (p->price_dates[p->price_no-1]) ;
  else cd = CALC_julian( date );
  
  if (fund != CL_all) sf = ef = fund;

  /* Initialize all funds */
  for (f = sf; f <= ef; f++)
    p->fund[f].units = p->fund[f].cost = p->fund[f].time = 0.0;

  /* Find the value of all transactions at purchase time */
  for ( t = 0; t < p->trans_no; t++ ) { 
    if (!date || ( p->trans[t].date <= date)) {
      f = CALC_alias_index ( p, p->trans[t].alias ); 
      if ( f >= 0 && f >= sf && f <= ef &&
	p->trans[t].date <= p->price_dates[p->price_no-1] ) {
        if ( p->trans[t].type[0] == DIVIDEND )
          p->fund[f].units += p->trans[t].amount;
	else {
	  pr = CALC_find_price ( p, CALC_alias_index(p,p->trans[t].alias), p->trans[t].date );

          if ( pr > 0.0 ) {
            if ( p->trans[t].type[0] == BUY ) {
              p->fund[f].units += p->trans[t].amount / pr;
	      p->fund[f].cost += p->trans[t].amount;
	      p->fund[f].time += (cd - CALC_julian(p->trans[t].date))
			      * p->trans[t].amount;
            }
            if ( p->trans[t].type[0] == SELL ) {
              p->fund[f].units -= p->trans[t].amount / pr;
	      p->fund[f].cost -= p->trans[t].amount;
	      p->fund[f].time -= (cd - CALC_julian(p->trans[t].date))
			      * p->trans[t].amount;
	    }
	  }
        }
      }
    }
  }
  /* Find their value for the specified day */
  for ( f = sf; f <= ef; f++) {
    if ( p->fund[f].units > 0.0 ) {
      if ( date == 0 ) pr = p->price[(p->price_no-1)*p->fund_no + f] ;
      else pr = CALC_find_price ( p, f, date ); 
      p->fund[f].value = pr * p->fund[f].units;
    }
    else p->fund[f].value = 0.0;
  }
}

/*
 * Print out a summary of the Account current status
 *
 * Input:	Portfolio
 *		Fund		CL_all		implies all including totals
 *				CL_totals	implies totals only
 *
 * Output:	-
 */
void CALC_summary ( Account *p, FundId fund )
{
  int f;
  float tv = 0.0;
  float tc = 0.0;
  float tt = 0.0;

  CALC_totals( p, 0, CL_all );
  printf("fund         units   avg curr.   total   total  gain    gain   days\n");
  printf("             owned  cost value    cost   value     %%       $     /$\n");
  for (f = 0; f < p->fund_no; f++) {
    if (p->fund[f].cost > 0.0 && (f == fund || fund == CL_all )) {
      printf("%10s:%7.2f %5.2f %5.2f %7.2f %7.2f %5.2f %7.2f %6.2f\n",
      p->fund[f].name,
      p->fund[f].units >0.0 ? p->fund[f].units : 0.0,
      p->fund[f].units >0.0 ? p->fund[f].cost/p->fund[f].units : 0.0,
      p->fund[f].units >0.0 ? p->fund[f].value/p->fund[f].units : 0.0,
      p->fund[f].cost,
      p->fund[f].value,
      p->fund[f].cost && p->fund[f].value ? (p->fund[f].value/p->fund[f].cost-1.0)*100.0 : 0.0,
      p->fund[f].value - p->fund[f].cost, 
      p->fund[f].cost && p->fund[f].value ? p->fund[f].time/p->fund[f].cost : 0.0 );
      tv += p->fund[f].value;
      tc += p->fund[f].cost;
      tt += p->fund[f].time;
    }
  }

  if (fund == CL_totals || fund == CL_all )
    printf("     Total:                    %7.2f %7.2f %5.2f %7.2f %6.2f\n",
        tc, tv, tc ? (tv/tc - 1.0) * 100.0 : 0.0, tv-tc, tc ? tt/tc : 0.0 );
}

/*
 * Printout a history of fund values, for two specified dates,
 *
 * Input:	Portfolio
 *			Startdate	0	implies first price date
 *                                     -n       implies last price date - n days
 *			Enddate		0	implies last price date
 *			Fund		CL_totals	implies totals only
 *
 * Output:	-
 */
void CALC_history ( Account *p, Date start, Date end, FundId fund )
{
  int i,j, date, f;
  float total, totalc, per, dy, tdy = 0.0;
  float last = 0.0, tlast =0.0, clast =0.0, ctlast = 0.0;

  i = 0;
  if ( start > 0 ) while ( i < p->price_no-1 && p->price_dates[i] < start ) i++;
  else i = 0;
  j = i;
  if ( end )  while ( j < p->price_no-1 && p->price_dates[j] < end ) j++;
  else j = p->price_no - 1;

  if ( start < 0 && (start + j) > 0 ) i = j + start;

  printf ("Fund values for %d %d\n",p->price_dates[i],p->price_dates[j]);
  printf ("date   current   total  gain    day    gain\n");
  printf ("         value    cost     %%     /$     day\n");

  for ( date = i; date <= j; date++) {
    CALC_totals (p, p->price_dates[date], CL_all );
    printf("%d ",p->price_dates[date]);
    totalc = total = tdy = 0.0;
    for ( f = 0; f < p->fund_no; f++ ) {
      total += p->fund[f].value;
      totalc += p->fund[f].cost;
	  per = p->fund[f].cost ? (100.0*p->fund[f].value/p->fund[f].cost)-100.0 : 0.0;
	  dy = p->fund[f].cost ? p->fund[f].time/p->fund[f].cost : 0.0;
	  tdy += p->fund[f].time;
	  if ( fund == f ) {
        printf("%7.2f %7.2f %5.2f %4.2f %7.2f\n",
		    p->fund[f].value, p->fund[f].cost, per, dy, p->fund[f].value - last - ( p->fund[f].cost - clast) );
	    last = p->fund[f].value;
	    clast = p->fund[f].cost;
	  }
    }
    per = totalc ? (100.0*total/totalc)-100.0 : 0.0;
	tdy = totalc ? tdy / totalc : 0.0;
	if ( fund == CL_totals )
      printf("%7.2f %7.2f %5.2f %4.2f %7.2f\n",
	      total, totalc, per, tdy, total - tlast - (totalc - ctlast));
	tlast = total;
	ctlast = totalc;
  }
}

/*
 * This function fills a data set
 *
 * Input:	Portfolio
 *   Type       CL_price, CL_value, CL_avg, CL_trend, CL_gain, CL_gainday, CL_perprice
 *   Start      Starting date of interest
 *   End        Ending date of interest
 *   Fund       A valid fund index
 *   Date       Array to receive Julian dates
 *   Data       Array to receive Values for date
 *
 * Output:	Number of days entered into the array
 */
int CALC_stats ( Account *p, Date start, Date end, FundId fund, int type,
		 float *date, float *data )
{
  int i,j,k,d;
  float last = 0.0, clast =0.0, pmax = 0.0;
  i = 0;
  if ( start ) while ( i < p->price_no - 1 && p->price_dates[i] < start )i++;
  else i = 0;
  j = i;
  if ( end ) while ( j < p->price_no - 1 && p->price_dates[j] < end ) j++;
  else j = p->price_no - 1;

  k = 0;
  if (type == CL_perprice ) pmax = CALC_max ( p, fund );
  if (pmax == 0.0 ) pmax = 1.0;
  for ( d = i; d <= j; d++) {
    date[k] = (float) CALC_julian(p->price_dates[d]);
    if (type == CL_price ) data[k] = CALC_find_price(p, fund, p->price_dates[d]);
    else if (type == CL_perprice ) data[k] = CALC_find_price(p, fund, p->price_dates[d])/ pmax * 100.0;
    else {
      CALC_totals (p, p->price_dates[d], fund );
      if (type == CL_value) data[k] = p->fund[fund].value;
      if (type == CL_cost) data[k] = p->fund[fund].cost;
      if (type == CL_avg)
        data[k] = CALC_avg_price(p,fund,p->price_dates[d],10);
      if (type == CL_trend)
        data[k] = k > 0 ? (CALC_avg_price(p,fund,p->price_dates[d],10) - CALC_avg_price(p,fund,p->price_dates[d-1],10)) : 0.0;
      if (type == CL_gain)
	data[k] = p->fund[fund].cost ? (100.0*p->fund[fund].value/p->fund[fund].cost-100.0) : 0.0;
      if (type == CL_gainday) {
        data[k] = p->fund[fund].value - last - ( p->fund[fund].cost - clast);
	last= p->fund[fund].value;
	clast = p->fund[fund].cost;
      }
    }
    if (DebugFlag) printf("[%d]%f\n",p->price_dates[d],data[k]);
    k++;
  }
  return k;
}

/*
 * Perform some test calls
 *
 * Input:	Portfolio
 *
 * Output:	-
 */
void CALC_test ( Account *p )
{
  printf("SA %d\n",CALC_alias_index(p,"SA"));
  printf("AF %d\n",CALC_alias_index(p,"AF"));
  printf("ST %d\n",CALC_alias_index(p,"ST"));
  printf("SA %d\n",CALC_alias_index(p,"SA"));
  printf("930223 %d\n",CALC_julian(930223));
  printf("930228 %d\n",CALC_date(CALC_julian(930228)));
  printf("930301 %d\n",CALC_julian(930301));
  printf("930331 %d\n",CALC_julian(930331));
  printf("921231 %d\n",CALC_date(CALC_julian(921231)));
  printf("930101 %d\n",CALC_julian(930101));
  printf("930115 EQ $ %5.2f\n",CALC_find_price(p,CALC_alias_index(p,"SA"),930115));
  printf("max SA %5.2f\n",CALC_max(p,CALC_alias_index(p,"SA")));
  printf("max IN %5.2f\n",CALC_max(p,CALC_alias_index(p,"IN")));
  printf("min IN %5.2f\n",CALC_min(p,CALC_alias_index(p,"IN")));
  CALC_history(p,930301,930329,CL_all);
  CALC_totals(p,0,-1);
  CALC_summary(p,-1);
  CALC_summary(p,0);
  CALC_summary(p,-2);
}
