/* ======================================================= *
 * Copyright 1998-2005 Stephen C. Grubb                    *
 * http://ploticus.sourceforge.net                         *
 * Covered by GPL; see the file ./Copyright for details.   *
 * ======================================================= */

#include "pl.h"
#define MAXFIELDS 50

int
PLP_autorange( axis, specline, minval, maxval )
char axis;
char *specline; /* spec line from proc areadef.. */
char *minval;   /* determined plot minima.. */
char *maxval;   /* determined plot maxima.. */
{
int i, j;
int df[MAXFIELDS];
int ndf;
char nearest[80];
char buf[256];
char dfield[256];
double min, max, fmod();
char smin[80], smax[80];
double fval;
int stat;
double margin;
int ix;
char lowfix[80], hifix[80];
char unittyp[80];
char floatformat[20];
/* char datepart[40], timepart[40]; */
double incmult;
char tok[80];
char selex[256]; /* added */
int selresult; /* added */
int combomode, first;
double hiaccum, loaccum;
int goodfound;
double submin, submax;
double fabs(), floor();
int mininit, maxinit;
int ffgiven;



/* see what scaletype is being used.. */
Egetunits( axis, unittyp );
if( strcmp( unittyp, "linear" )==0 ) strcpy( nearest, "auto" );
else if ( GL_smemberi( unittyp, "date time datetime" )) strcpy( nearest, "datematic" );
else strcpy( nearest, "exact" ); /* categories? */

margin = 0.0;
strcpy( dfield, "" );
ix = 0;
strcpy( lowfix, "" );
strcpy( hifix, "" );
strcpy( floatformat, "%g" );  /* changed scg 10/1/03 .. scientific formats (e+ and e-) should be ok now */
ffgiven = 0;
incmult = 1.0;
strcpy( selex, "" ); /* added */
combomode = 0;
mininit = maxinit = 0;

while( 1 ) {
        strcpy( buf, GL_getok( specline, &ix ) );
        if( buf[0] == '\0' ) break;
        if( strnicmp( buf, "datafields", 10 )==0 ) strcpy( dfield, &buf[11] );
        else if( strnicmp( buf, "datafield", 9 )==0 ) strcpy( dfield, &buf[10] ); 
        else if( strnicmp( buf, "incmult", 7 )==0 ) incmult = atof( &buf[8] );
        else if( strnicmp( buf, "nearest", 7 )==0 ) strcpy( nearest, &buf[8] );
        else if( strnicmp( buf, "margin", 6 )==0 ) margin = atof( &buf[7] );
        else if( strnicmp( buf, "lowfix", 6 )==0 ) strcpy( lowfix, &buf[7] );
        else if( strnicmp( buf, "hifix", 5 )==0 ) strcpy( hifix, &buf[6] );
        else if( strnicmp( buf, "mininit", 7 )==0 ) { strcpy( lowfix, &buf[8] ); mininit = 1; }
        else if( strnicmp( buf, "maxinit", 7 )==0 ) { strcpy( hifix, &buf[8] ); maxinit = 1; }
        else if( strnicmp( buf, "numformat", 9 )==0 ) { strcpy( floatformat, &buf[10] ); ffgiven = 1; }
        else if( strnicmp( buf, "combomode", 9 )==0 ) {
		if( stricmp( &buf[10], "stack" )==0 ) combomode = 1;
		else if( stricmp( &buf[10], "hilo" )==0 ) combomode = 2;
		else combomode = 0;
		}
        else if( strnicmp( buf, "selectrows", 10 )==0 ) {
                strcpy( selex, &buf[11] );
                strcat( selex, &specline[ix] );
                break;
                }
	else Eerr( 5702, "unrecognized autorange subattribute", buf );
	}

/* fill array of df from comma-delimited list - contributed by Paul Totten <pwtotten@nortelnetworks.com> */
ndf = 0; ix = 0; 
while( 1 ) {
	GL_getseg( tok, dfield, &ix, "," );
	if( tok[0] == '\0' ) break;
	if( ndf >= (MAXFIELDS-1) ) break;
	df[ndf] = fref( tok ) - 1;
	if( df[ndf] < 0 || df[ndf] >= Nfields ) continue;
	ndf++;
	}

/* df = fref( dfield ) - 1; */

if( Nrecords < 1 ) return( Eerr( 17, "autorange: no data set has been read/specified w/ proc getdata", "" ) );
if( axis == '\0' ) return( Eerr( 7194, "autorange: axis attribute must be specified", "" ) );
if( ndf == 0 ) return( Eerr( 7194, "autorange: datafield omitted or invalid ", dfield ) );


/* ------------------ */
/* now do the work.. */
/* ----------------- */

/* override.. */
/* if( stricmp( nearest, "day" )==0 && stricmp( unittyp, "date" )==0 ) strcpy( nearest, "exact" ); */ /* removed scg 4/19/05 */

/* find data min and max.. */


/* initialize.. */
if( mininit ) min = Econv( axis, lowfix );
else min = PLHUGE;
if( maxinit ) max = Econv( axis, hifix );
else max = NEGHUGE;

for( i = 0; i < Nrecords; i++ ) {

        if( selex[0] != '\0' ) { /* added scg 8/1/01 */
                stat = do_select( selex, i, &selresult );
                if( selresult == 0 ) continue;
                }


	hiaccum = 0.0; loaccum = 0.0; first = 1; goodfound = 0; submin = PLHUGE; submax = NEGHUGE;

	for( j = 0; j < ndf; j++ ) {  /* for all datafields to be examined.. */
		fval = fda( i, df[j], axis );
		if( Econv_error() ) continue;
		goodfound = 1;
		if( !combomode ) {
			if( fval < submin ) submin = fval;
			if( fval > submax ) submax = fval;
			}
		else 	{
			hiaccum += fval;
			if( combomode == 2 ) { /* hilo */
				if( first ) { loaccum = fval; first = 0; }
				else loaccum -= fval;
				}
			else	{ /* stack */
				if( first ) { loaccum = fval; first = 0; }
				}
			}
		}
	if( !goodfound ) continue;

	if( combomode ) {
		if( loaccum < min ) min = loaccum;
		if( hiaccum > max ) max = hiaccum;
		}
	else	{
		if( submin < min ) min = submin;
		if( submax > max ) max = submax;
		}
	}


/* If user didn't specify "numformat", try to be "smart" about whether to use %g or %f for building result..         *
 * %g is usually best but in certain cases (high magnitude low variance data) %f should be used.  Added scg 8/10/05. */
if( !ffgiven && fabs( min ) > 100000 && fabs( max ) > 100000 && ( max - min < 1000 )) strcpy( floatformat, "%f" );


/* now convert min and max to current units and then to nearest interval.. */
if( strcmp( unittyp, "linear" )==0 ) { /* avoid using Euprint()- trouble w/ v. big or v. small #s */
	sprintf( smin, floatformat, min );
	sprintf( smax, floatformat, max );
	}
else	{
	Euprint( smin, axis, min, "" );
	Euprint( smax, axis, max, "" );
	}

/* save data min/max.. */
if( axis == 'x' ) {
	setcharvar( "DATAXMIN", smin );
	setcharvar( "DATAXMAX", smax );
	}
else 	{
	setcharvar( "DATAYMIN", smin );
	setcharvar( "DATAYMAX", smax );
	}


/* now adjust for margin.. */
min -= margin;
max += margin;


/* degenerate case.. all data the same (bad if it happens to lie on inc boundary, eg: 0) - added scg 9/21/01 */
if( min == max ) {
	/* min = min - 1.0; max = max + 1.0; */
	min -= fabs(min*0.2); max += fabs(max*0.2);  /* changed to work better w/ small magnitude values - scg 3/3/05 */
					             /* now uses fabs().. to handle degen case of a single neg. data value- scg 7/31/05 */
	if( min == max ) { min = min - 1.0; max = max + 1.0; } /* this kicks in if min=0 and max=0 - scg 7/6/05 */
	}


/* and do the conversion with margin.. */
if( strcmp( unittyp, "linear" )==0 ) { /* avoid using Euprint()- trouble w/ v. big or v. small #s */
	sprintf( smin, floatformat, min );
	sprintf( smax, floatformat, max );
	}
else	{
	Euprint( smin, axis, min, "" );
	Euprint( smax, axis, max, "" );
	}


/******* handle nearest=  ***********/

if( GL_slmember( nearest, "dat*matic" ) ) {
	char foo1[40], foo2[40], foo3[40], foo4[40], foo5[40], foo6[40];
	double dfoo1, dfoo2;
	/* get an automatic reasonable "nearest" value.. */
	DT_reasonable( unittyp, min, max, &dfoo1, foo1, foo2, foo3, foo4, foo5, &dfoo2, foo6, nearest );
	}


if( stricmp( nearest, "exact" )==0 ) {  /* exact */
	sprintf( minval, "%s", smin ); 
	sprintf( maxval, "%s", smax ); 
	}

else if( PLP_findnearest( smin, smax, axis, nearest, minval, maxval ) );


#ifdef CUT

else if( strnicmp( nearest, "month", 5 )== 0 || strnicmp( nearest, "quarter", 7 )==0 || strnicmp( nearest, "3month", 6 )==0 ) { 
	/* nearest month boundary / quarter-year boundary.. */
	int mon, day, yr, newmon;
	long l;
	if( !GL_smember( unittyp, "date datetime" )) 
		Eerr( 2892, "autorange 'nearest=month' or 'nearest=quarter' only valid with date or datetime scaletype", unittyp );
	/* min */
	stat = DT_jdate( smin, &l );
        DT_getmdy( &mon, &day, &yr );
	if( tolower( nearest[0] ) == 'q' || nearest[0] == '3' ) {
		if( mon >= 10 ) mon = 10;
		else if( mon >= 7 ) mon = 7;
		else if( mon >= 4 ) mon = 4;
		else if( mon >= 1 ) mon = 1;
		}
	DT_makedate( yr, mon, 1, "", datepart );
	if( strcmp( unittyp, "datetime" )==0 ) {
		DT_maketime( 0, 0, 0.0, timepart );
		DT_build_dt( datepart, timepart, minval );
		}
	else strcpy( minval, datepart );

	/* max */
	stat = DT_jdate( smax, &l );
        DT_getmdy( &mon, &day, &yr );
	if( tolower( nearest[0] ) == 'q' || nearest[0] == '3' ) {
		if( mon <= 3 ) mon = 4;
		else if( mon <= 6 ) mon = 7;
		else if( mon <= 9 ) mon = 10;
		else if( mon <= 12 ) mon = 13;
		}
	else mon ++;

        /* wrap around year.. */
        newmon = ((mon-1) % 12 ) +1;
        yr += ((mon-1) / 12);
        mon = newmon;
	DT_makedate( yr, mon, 1, "", datepart );
	if( strcmp( unittyp, "datetime" )==0 ) {
		DT_maketime( 0, 0, 0.0, timepart );
		DT_build_dt( datepart, timepart, maxval );
		}
	else strcpy( maxval, datepart );
	}


else if( strnicmp( nearest, "year", 4 )== 0 || strnicmp( nearest, "2year", 5 )==0 || 
	strnicmp( nearest, "5year", 5 )==0 || strnicmp( nearest, "10year", 6 )==0 ) { 
	int mon, day, yr;
	long l;
	int yearsblock; /* 0 5 or 10 */

	if( !GL_smember( unittyp, "date datetime" )) 
		Eerr( 2892, "autorange 'nearest=year' only valid with date or datetime scaletype", unittyp );

	if( tolower( (int) nearest[0] ) != 'y' ) {			/* this section scg 1/28/05 */
		yearsblock = nearest[0] - '0';
		if( yearsblock == 1 ) yearsblock = 10;
		}
	else yearsblock = 0;

	/* min */
	stat = DT_jdate( smin, &l );
        DT_getmdy( &mon, &day, &yr );
	if( yearsblock ) yr = (yr / yearsblock) * yearsblock; 		/* scg 1/28/05 */
	DT_makedate( yr, 1, 1, "", datepart );
	if( strcmp( unittyp, "datetime" )==0 ) {
		DT_maketime( 0, 0, 0.0, timepart );
		DT_build_dt( datepart, timepart, minval );
		}
	else strcpy( minval, datepart );
		
	/* max */
	stat = DT_jdate( smax, &l );
        DT_getmdy( &mon, &day, &yr );
	if( yearsblock ) yr = ((yr / yearsblock)+1) * yearsblock; 		/* scg 1/28/05 */
	else yr++;
	DT_makedate( yr, 1, 1, "", datepart );
	if( strcmp( unittyp, "datetime" )==0 ) {
		DT_maketime( 0, 0, 0.0, timepart );
		DT_build_dt( datepart, timepart, maxval );
		}
	else strcpy( maxval, datepart );
	}


else if( strnicmp( nearest, "day", 3 )== 0 || stricmp( nearest, "monday" )==0 || stricmp( nearest, "sunday" )==0 ) { 
	int mon, day, yr;
	double days, mins;

	if( !GL_smember( unittyp, "date datetime" )) 
		Eerr( 2892, "autorange 'nearest=day' only valid with date or datetime scaletype", unittyp );

	/* min */
	if( strcmp( unittyp, "datetime" )==0 ) DT_getdtparts( smin, datepart, timepart ); 
	else strcpy( datepart, smin ); /* if and else added scg 8/10/05 */

	if( tolower( nearest[0] ) == 'm' || tolower( nearest[0] ) == 's' ) {  /* adjust datepart back to a monday or sunday */
		int iwk;  char rbuf[40];
		DT_weekday( datepart, rbuf, &iwk ); /* rbuf not used */
		if( tolower( nearest[0] ) == 'm' ) { if( iwk == 1 ) iwk = 8; DT_dateadd( datepart, 2 - iwk, rbuf ); }
		else if( tolower( nearest[0] ) == 's' ) DT_dateadd( datepart, 1 - iwk, rbuf );
		strcpy( datepart, rbuf );
		}

	/* this is just a way to get the dt parts (?) ...
	 * DT_datetime2days( smin, &days ); 
         * DT_getmdy( &mon, &day, &yr );
	 * DT_makedate( yr, mon, day, "", datepart );
	 */

	if( strcmp( unittyp, "date" )==0 ) 
	/* check for biz day window.. scg 7/21/04 */
	mins = 0.0;
	DT_frame_mins( &mins ); /* adjust to any biz day window.. */
	DT_frommin( mins, timepart ); 
	if( strcmp( unittyp, "date" )==0 ) strcpy( minval, datepart );
	else DT_build_dt( datepart, timepart, minval );

	/* max */
	if( strcmp( unittyp, "datetime" )==0 ) DT_getdtparts( smax, datepart, timepart );
	else strcpy( datepart, smax );  /* if and else added scg 8/10/05 */

	if( tolower( nearest[0] ) == 'm' || tolower( nearest[0] ) == 's' ) {  /* adjust datepart to next monday or sunday */
		int iwk;  char rbuf[40];
		DT_weekday( datepart, rbuf, &iwk ); /* rbuf not used */
		if( tolower( nearest[0] ) == 'm' ) { if( iwk == 1 ) iwk = 8; DT_dateadd( datepart, 9 - iwk, rbuf ); }
		else if( tolower( nearest[0] ) == 's' ) DT_dateadd( datepart, 8 - iwk, rbuf );
		DT_build_dt( rbuf, timepart, smax );
		}

	DT_datetime2days( smax, &days );
	if( fabs( days - floor( days )) < 0.0001 ) ; /* avoid spurious extra day when data max is on date boundary added scg 7/21/04 */
	else days++;
	DT_days2datetime( days, smax ); 
	DT_datetime2days( smax, &days ); /* set next day's date for getmdy below */
        DT_getmdy( &mon, &day, &yr );
	DT_makedate( yr, mon, day, "", datepart );
	DT_maketime( 0, 0, 0.0, timepart );
	if( strcmp( unittyp, "date" )==0 ) strcpy( maxval, datepart );
	else DT_build_dt( datepart, timepart, maxval );
	}

else if( strnicmp( nearest, "hour", 4 )== 0 || strnicmp( nearest, "3hour", 5 )==0 ||
	 strnicmp( nearest, "6hour", 5 )==0 || strnicmp( nearest, "12hour", 6 )==0 ) {
	int hour, minute;
	double sec;
	int hoursblock; /* 0, 3, 6, or 12 */
	if( !GL_smember( unittyp, "time datetime" )) 
		Eerr( 2892, "autorange 'nearest=hour' is incompatible with scaletype", unittyp );

	if( tolower( (int) nearest[0] ) != 'h' ) {				/* this section scg 1/28/05 */
		hoursblock = nearest[0] - '0';
		if( hoursblock == 1 ) hoursblock = 12;
		}
	else hoursblock = 0;

	if( strcmp( unittyp, "time" )==0 ) {
		/* min */
		DT_tomin( smin, &sec ); /* sec not used */
		DT_gethms( &hour, &minute, &sec );
		if( hoursblock ) hour = (hour / hoursblock) * hoursblock; 		/* scg 1/28/05 */
		DT_maketime( hour, 0, 0.0, minval );
		/* max */
		DT_tomin( smax, &sec ); /* sec not used */
		DT_gethms( &hour, &minute, &sec );
		if( hoursblock ) hour = ((hour / hoursblock)+1) * hoursblock; 		/* scg 1/28/05 */
		if( minute != 0 || sec != 0.0 ) hour++; 				/* bug, scg 12/13/05 */
		DT_maketime( hour, 0, 0.0, maxval ); 
		}
	else if( strcmp( unittyp, "datetime" )==0 ) {
		double days;
		int mon, day, yr;

		/* min */
		DT_datetime2days( smin, &days );
		/* time part */
		DT_gethms( &hour, &minute, &sec );
		if( hoursblock ) hour = (hour / hoursblock) * hoursblock; 		/* scg 1/28/05 */
		DT_maketime( hour, 0, 0.0, timepart );
		/* date part */
        	DT_getmdy( &mon, &day, &yr );
		DT_makedate( yr, mon, day, "", datepart );
		DT_build_dt( datepart, timepart, minval );

		/* max */
		DT_datetime2days( smax, &days );
		/* time part */
		DT_gethms( &hour, &minute, &sec );
		if( hour == 23 ) {
			DT_days2datetime( days+1.0, smax ); /* set next day's date for getmdy below*/
			DT_datetime2days( smax, &days ); /* set next day's date for getmdy below*/
			DT_maketime( 0, 0, 0.0, timepart );					/* ok for any hoursblock */
			}
		else 	{
			if( hoursblock ) hour = ((hour / hoursblock)+1) * hoursblock; 		/* scg 1/28/05 */
			else hour++;
			DT_maketime( hour, 0, 0.0, timepart );
			}
		/* date part */
        	DT_getmdy( &mon, &day, &yr );
		DT_makedate( yr, mon, day, "", datepart );
		DT_build_dt( datepart, timepart, maxval );
		}
	}

else if( stricmp( nearest, "minute" )==0 || stricmp( nearest, "10minute" )==0 || 
	stricmp( nearest, "20minute" )==0 || stricmp( nearest, "30minute" )==0 ) {		/* this section scg 1/28/05 */
	int hour, minute, minblock;
	double sec;

	if( strcmp( unittyp, "time" )!= 0 ) Eerr( 2892, "autorange 'nearest=minute' is incompatible with scaletype", unittyp );

	if( tolower( (int) nearest[0] ) != 'm' ) minblock = (nearest[0] - '0') * 10;
	else minblock = 0;

	/* min */
	DT_tomin( smin, &sec ); /* sec not used */
	DT_gethms( &hour, &minute, &sec );
	if( minblock ) minute = (minute / minblock) * minblock; 		
	DT_maketime( hour, minute, 0.0, minval );

	/* max */
	DT_tomin( smax, &sec ); /* sec not used */
	DT_gethms( &hour, &minute, &sec );
	if( minblock ) minute = ((minute / minblock)+1) * minblock; 	
	else minute++;
	if( minute >= 60 ) { minute = minute % 60; hour++; }
	DT_maketime( hour, minute, 0.0, maxval );
	}


#endif
else 	{      /* this section added scg 7/5/00 */
	double inc, h, fmod(), a, b;

	if( strcmp( nearest, "auto" )==0 ) PL_defaultinc( min, max, &inc );
	else inc = atof( nearest );


	h = fmod( min, inc );

	a = (min - h) - inc;
	b = a - inc;    /* include one extra inc on low end */
	if( a >= 0.0 && b < 0.0 ) b = a;  /* but don't dip below 0 */

	if( min < 0.0 ) sprintf( minval, floatformat, (min - h) - (inc * incmult) ); /* include extra inc on low end - scg 11/29/00 */
	else 	{
		a = min - h;
		b = a - (inc*(incmult-1.0)); /* include extra inc on low end - 11/29 */
		if( a >= 0.0 && b < 0.0 ) b = a;  /* but don't dip below 0  - 11/29 */
		sprintf( minval, floatformat, b );
		}

	h = fmod( max, inc );
	if( max < 0.0 ) sprintf( maxval, floatformat, (max - h) + (inc*(incmult-1.0)) ); /* include extra inc on high end - 11/29 */
	else sprintf( maxval, floatformat, (max - h) + (inc * incmult) ); /* extra inc - 11/29 */
	}



/* lowfix and hifix overrides.. */
if( lowfix[0] != '\0' && !mininit ) strcpy( minval, lowfix );
if( hifix[0] != '\0' && !maxinit ) strcpy( maxval, hifix );

/* be sure result makes sense.. for instance, if lowfix=0 specified but data are all negative,
	min will be 0 but max will be eg. -3.... patch that up here to avoid areadef crash&burn 
	(automated situations) - scg 3/25/04 */
if( GL_slmember( unittyp, "linear log*" ) && atof( maxval ) <= atof( minval ) ) {
	Eerr( 5709, "autorange: all data out of range", "" );
	strcpy( minval, "0" );
	strcpy( maxval, "1" );
	}

if( PLS.debug )
  fprintf( PLS.diagfp, "Autorange on %c: min=%s to max=%s\n", axis, minval, maxval);

DT_suppress_twin_warn( 1 ); /* suppress complaints about datetime outside of window 
				until after areadef */
return( 0 );
}

/* ======================================================= *
 * Copyright 1998-2005 Stephen C. Grubb                    *
 * http://ploticus.sourceforge.net                         *
 * Covered by GPL; see the file ./Copyright for details.   *
 * ======================================================= */


syntax highlighted by Code2HTML, v. 0.9.1