/*===========================================================================
  Copyright (C) 1995-2009 European Southern Observatory (ESO)
 
  This program 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 2 of 
  the License, or (at your option) any later version.
 
  This program 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 this program; if not, write to the Free 
  Software Foundation, Inc., 675 Massachusetts Ave, Cambridge, 
  MA 02139, USA.
 
  Correspondence concerning ESO-MIDAS should be addressed as follows:
	Internet e-mail: midas@eso.org
	Postal address: European Southern Observatory
			Data Management Division 
			Karl-Schwarzschild-Strasse 2
			D 85748 Garching bei Muenchen 
			GERMANY
===========================================================================*/

/*
 * HEADER : aglaxutl.c     - Vers 3.6.006  - Nov 1993 -  L. Fini, OAA
 *                         - Vers 3.6.005  - Sep 1993 -  L. Fini, OAA
 *                         - Vers 3.6.004  - Jul 1993 -  L. Fini, OAA
 *
 * Utilities for axes routines
 */

/*
.VERSION

090416		last modif
*/


#include <aglenvr.h>
#include <aglaxes.h>


/*****************************************************************************/
/*                                AGLCleanFloat                              */

/* Gets an input string of the form 1.00 and returns the cleaned form        */
/* without all those trailing zeroes                                         */

static void AGLCleanFloat(input,output,cleanall)
char *input;                     /* Input string +/-nnn.ddd                 */
char *output;                    /* Output string (AGL text format)         */
int cleanall;			/* if TRUE also clear decimal point         */
{
char *pt;

pt = strchr(input,'\0')-1; 

while(pt>input)
	if(*pt == '0')
		pt--;
	else 
            {
	    if (*pt =='.') 
               {
	       if (cleanall)
			pt--;
		else
			pt++;
	       }
            break;
	}
	
while(input<=pt)
	*output++ = *input++;

*output = '\0';
}


/*****************************************************************************/
/*                                AGLCleanExp                              */

/* Gets an input string of the form 12.345e+678 and returns the exp form with*/
/* 10 to 678 using the AGL text metacharacter sequences.                     */

static void AGLCleanExp(input,output)
char *input;			/* Input string exponential format           */
char *output;			/* Output string (AGL text format)           */
{
char *src;

src = strchr(input,'e');
*src++ ='\0'; 
AGLCleanFloat(input,output,FALSE);
if(strcmp(output,"0.0") != 0) {
	strcat(output," 10~{~^");

	if(*src == '-')
		strcat(output,"-");;

	src++;				/* drop sign                         */

	while(*src)
		if(*src == '0') 
			src++;		/* drop leading 0's                  */
		else
			break;

	if(*src == '\0') 
		src--;

	strcat(output,src);
	strcat(output,"~}");
} 
}



/*****************************************************************************/
/*                                AG_round                                   */

/* Roundoff to long integer                                                  */

static long AG_round(dval)
double dval;				/* Value to roundoff                 */
{
int sign;
sign = (dval<0.0)?(-1):(1);
return (sign*floor(fabs((double)dval)+0.5));
}



static void bestfmtd(axis)		/* Return format spec      */
struct AXIS *axis;
{
double qmax,qmin,divb,dfact,doffst;
double ipmax,ipmin;

dfact = axis->fact; doffst = axis->offst;

divb = (axis->tikmj * dfact) + doffst;
qmax = (axis->ltik * dfact) + doffst;
qmin = (axis->ftik * dfact) + doffst;

qmax = fabs(qmax);
qmin = fabs(qmin);
qmax = MAX(qmax,qmin);
qmin = MIN(qmax,qmin);

divb = fabs(divb);

qmin = MIN(qmin,divb);
qmin = (qmin==0.0)?divb:qmin;

qmax *= 1.01;
qmin *= 1.01;

ipmax = floor(log10(qmax));
ipmin = floor(log10(qmin));

axis->iexp=0.0;

if( (ipmax>3) || (ipmax<(-2)) ) {
	if(fabs((ipmax-ipmin))<=4.0) {
		axis->iexp=ipmin;
		strcpy(axis->form,"%5.2f");
	} else {
		strcpy(axis->form,"%e");
	}
} else 
	if(ipmin>=0) 
		strcpy(axis->form,"%d");
	else {
		int ndigits=fabs(ipmin);
		sprintf(axis->form,"%%.%df",ndigits);
	}
}

static double agltoint(val,sg)	/* return closest integer, taking rounding  */
				/* errors into account                      */
double val;			/* value to round up (alwais >=0)           */
double sg;			/* if <0 then return floor, else ceiling    */
{
double aux;
double fr=val*1e-4;		/* value to check for "close" integers      */

if(sg<0) {
	aux=floor(val);
	if((aux+1.0-val)<fr)
		aux += 1.0;
} else {
	aux=ceil(val);
	if((val+1.0-aux)<fr)
		aux -= 1.0;
}

return aux;
}


static void frslst(qmin,qmax,div,f,l) /* Comp. first and last main division  */
double qmin;                          /* Quotes lower limit                 */
double qmax;                          /* Quotes upper limit                 */
double div;                           /* Division step                      */
float *f,*l;                          /* Computed values                    */
{
double smin,smax;
double aqmin,aqmax;
double fr=div*0.1;

if(qmin<0.0) {				/* Use absolute value               */
	aqmin = -qmin;
	smin = -1;
} else {
	aqmin = qmin;
	smin=1;
}

if(qmax<0.0) {				/* Use absolute value               */
	aqmax = -qmax;
	smax = -1;
} else {
	aqmax=qmax;
	smax=1;
}

if(div!=0) {
	aqmin /= div;
	aqmax /= div;
}

aqmin = agltoint(aqmin,smin) * div * smin;
aqmax = agltoint(aqmax,-smax) * div * smax;

if((qmin-aqmin)>fr)
	aqmin += div;

if((aqmax-qmax)>fr)
	aqmax -= div;

*f = aqmin;
*l = aqmax;
}





/*****************************************************************************/
/*                              AGLfmtd                                      */

/* This module computes the best format and tik positions suited for decimal */
/* linear quotes along an axis.                                              */

static void AGLfmtd(axis)
struct AXIS *axis;		/* Axis structure                           */
{
static double tresh[2] = { 1.7, 3.6 };
double qmax,qmin,range;
double powdiv,decade;
double divb=0.0,divs=0.0;

int i;



qmax = MAX(axis->divs[0],axis->divs[1]);
qmin = MIN(axis->divs[0],axis->divs[1]);

switch(axis->man) {

case AUTO:
	range = (qmax-qmin);

	decade = floor(log10(range));
	powdiv = exp(decade*LOG10);
	range = range / powdiv; 	/* Fact:: 1.0 <= range < 10.0 */

	for(i=0;i<2;i++)
		if(range<tresh[i]) break;

	switch(i) {

	case 0: divb = 0.2; divs=0.05;  break;
	case 1: divb = 0.5; divs=0.1;  break;
	case 2: divb = 1.0; divs=0.2;  break;
	}
 
	divb = divb * powdiv;
	axis->tikmj = divb;
	axis->tikmn = divs * powdiv;
	break;

case MANUAL:
	divb = axis->tikmj;

default:
	break;
}

if((axis->man != NONE)&&(divb>0.0)) {
	frslst(qmin,qmax,divb,			/* Compute first, last tik   */
	       &(axis->ftik),&(axis->ltik));

	if(*axis->form != '*')
		bestfmtd(axis);			/* Return format spec */
}
}




/*****************************************************************************/
/*                              AGLfmta                                      */

/* This module computes the best format and tik positions suited for angular */
/* linear quotes.                                                            */

static void AGLfmta(axis)
struct AXIS *axis;		/* Axis structure                           */
{
static double tresh[6] = { 1.1, 2.1, 3.1, 7.0, 13.0, 34.0  };
int ipmax,ipmin,i;

double sexade;
double powdiv;
double qmax,qmin,range,divb=0.0,divs;

qmax = MAX(axis->divs[0],axis->divs[1]);
qmin = MIN(axis->divs[0],axis->divs[1]);

switch(axis->man) {

case AUTO:
	range = (qmax-qmin);
	sexade = floor(log((double)range)/(double)LOG60);
	powdiv = exp((double)(sexade*LOG60));
	range = range/powdiv;		/* Fact:: 1 <= range <  60 */

	for(i=0;i<6;i++)
		if(range<tresh[i]) break;

	switch(i) {
		case 0:  			/* < 1.1  */
			divb = 0.16666666667; 
			divs=0.0333333333333; 
			break; 
		case 1:  			/* < 2.1  */
			divb = 0.33333333333; 
			divs=0.0833333333333; 
			break; 
		case 2:  			/* < 3.1  */
			divb = 0.5;           
			divs=0.1666666666667; 
			break; 
		case 3:  			/* < 7.0  */
			divb = 1.0;           
			divs=0.3333333333333; 
			break; 
		case 4:  			/* <13.0  */
			divb = 2.0;           
			divs=0.5;             
			break; 
		case 5:  			/* <34.0  */
			divb = 5.0;           
			divs=1.0;             
			break; 
		default: 			/* <60.0  */
			divb = 10.0;          
			divs=2.0;             
			break; 
	}
  
	divb = divb * powdiv;
	axis->tikmj = divb;
	axis->tikmn = divs * powdiv;
	break;

case MANUAL:
	divb = axis->tikmj;

default:
	break;
}


if(axis->man!=NONE) {
	frslst(qmin,qmax,divb,		/* Compute first and last tik */
	       &(axis->ftik),&(axis->ltik));

					/* Now find out the best format       */
  
	qmax = (axis->ltik * axis->fact) + axis->offst;
	qmin = (axis->ftik * axis->fact) + axis->offst;
	qmax = fabs((double)qmax);
	qmin = fabs((double)qmin);
	qmax = MAX(qmax,qmin);
 
	qmax *= 1.01;
	divb *= 1.01;
   
	ipmax = floor(log((double)qmax)/(double)LOG60);
	ipmin = floor(log((double)divb)/(double)LOG60);
 
	if(*axis->form != '*') {
		if(ipmin>=0) {
			bestfmtd(axis);
			return;
		}
 
		if(ipmax>=0) {
			if(ipmin==(-1)) { 
				strcpy(axis->form,"%am"); 
				return; 
			}
			if(ipmin==(-2)) { 
				strcpy(axis->form,"%as"); 
				return; 
			}
			*axis->form = '\0'; 
			return; 
		}

		if(ipmax>(-2)) { 
			strcpy(axis->form,"%ms"); 
			return; 
		}

		strcpy(axis->form,"%ss"); 
	}
}
}



/*****************************************************************************/
/*                              AGLfmtl                                      */

/* This module computes the best format and tik positions suited for log     */
/* quotes.                                                                   */

static void AGLfmtl(axis)
struct AXIS *axis;		/* Axis structure                           */
{
double qmax,qmin,divb,divs;
double q0,q1;
int ndecs,i;
static int dectrsh[7] = { 6,10,15,23,47,63,95 };

q0 = axis->divs[0]*axis->fact + axis->offst;
q1 = axis->divs[1]*axis->fact + axis->offst;

qmax = MAX(q0,q1); qmax = log10((double)qmax);
qmin = MIN(q0,q1); qmin = log10((double)qmin);
   
switch(axis->man) {

case AUTO:
	ndecs = qmax-qmin+1;

                                          /* get optim. divisions            */
	for(i=0;i<6;i++)
		if(ndecs<dectrsh[i]) break;

	switch(i) {

	case 0:  				/*  < 6                */
		divb = 1.0;  
		divs = 1.0;    
		break;
	case 1:  				/*  < 10               */
		divb = 1.0;  
		divs = 5.00;   
		break;
	case 2:  				/*  < 15               */
		divb = 2.0;  
		divs = 10.0;   
		break;
	case 3:  				/*  < 23               */
		divb = 3.0;  
		divs = 10.0;   
		break;
	case 4:  				/*  < 47               */
		divb = 6.0;  
		divs = 100.00; 
		break;
	case 5:  				/*  < 63               */
		divb = 9.0;  
		divs = 1000.0; 
		break;
	case 6:  				/*  < 95               */
		divb = 12.0; 
		divs = 1000.0; 
		break;
	default: 
		divb = 15.0; 
		divs = 1000.0; 
		break; 
	}
	axis->tikmn = divs;
	axis->tikmj = exp((double)(divb*LOG10));
	break;

case MANUAL:
	divb = floor(log10((double)axis->tikmj));
	divb = (divb<=0) ? 1 : divb;
	break;

default:
	break;
}

if(axis->man != NONE) {
	frslst(qmin,qmax,divb,			/* Compute first, last tik   */
	       &(axis->ftik),&(axis->ltik));

	axis->ftik = exp((double)(axis->ftik * LOG10));
	axis->ltik = exp((double)(axis->ltik * LOG10));

	if(*axis->form != '*')
		strcpy(axis->form,"%p"); 
}
}

/*****************************************************************************/
/*                              AGLfmtp                                     */

/* This module computes the best format and tik positions suited for quotes  */
/* expressed as multiples of PI                                              */


static void AGLfmtp(axis)
struct AXIS *axis;
{
float aux,n;
float qmax,qmin;
float angfact;

angfact = AGLAngF(axis->Angmode);

axis->tikmj=30*angfact;			/* PI/6   (30 degrees)  */
axis->tikmn=15*angfact;			/* PI/12  (15 degrees)  */

qmin=MIN(axis->divs[0],axis->divs[1]);
qmax=MAX(axis->divs[0],axis->divs[1]);

aux = qmin/axis->tikmj; 			/* first tik                */
n = floor(aux);
n = ((aux-n)<=1.e-4)?n:(n+1.0);
axis->ftik = n * axis->tikmj;

axis->ltik = floor(qmax/axis->tikmj+1.e-5) * 	/*   last tik               */
             axis->tikmj;

if (*axis->form != '*')
   {
	if(axis->Angmode==RADIANS)
		strcpy(axis->form,"%5.2f");
	else
		strcpy(axis->form,"%d");
   }
}





/*****************************************************************************/
/*                              AGLfmt                                      */

/* This module computes the best format and tik positions suited for the     */
/* axis mode of quotation                                                    */

void AGLfmt(axis)
struct AXIS *axis;		/* Axis structure                           */
{
switch(axis->mode) {

case LIN:
	AGLfmtd(axis);
	break;

case ANG:
	switch(axis->Angmode) {
	case RADIANS:
		AGLfmtp(axis);
		break;
	case HOURS:
		AGLfmta(axis);
		break;
	case DEGREES:
		AGLfmta(axis);
		break;
	}
	break;
case LOG:
	AGLfmtl(axis);
	break;
default:
	break;
}

}


/*****************************************************************************/
/*                              AGLAngF                                      */

/* This module computes the factor to convert a given angular representation */
/* into radians                                                              */

double AGLAngF(angmode)
enum ANGLES angmode;
{
switch(angmode) {

default:
case RADIANS:
	return 1.0;

case DEGREES:
	return .017453292519943295769236907684;

case HOURS:
	return .261799387799149436538553615273;
}
}


/*****************************************************************************/
/*                               AGLSex                                    */

/* Convert a value into sexagesimal format                                   */

#define MODF(i,v,base) v*=(base); i=(int)(v); v-=(i)

static void AGLSex(ostr,form,value)
char *ostr;			/* output string (it must be long enough)    */
char *form;			/* format specifier                          */
				/*  %au   (S)   : Degrees                    */
				/*  %aum  (S)   : Degrees minutes            */
				/*  %aums (S)   : Degrees minutes seconds    */
				/*  %ams  (S)   : minutes seconds            */
				/*  %amss (S)   : minutes seconds fraction   */
				/*  %hu   (S)   : Hours                      */
				/*  %hum  (S)   : Hours minutes              */
				/*  %hums (S)   : Hours minutes seconds      */
				/*  %hms  (S)   : minutes seconds            */
				/*  %hmss (S)   : minutes seconds fraction   */
				/* Add more s' to the end to get greather    */
				/* precision                                 */
double value;			/* value to convert                          */
{
static char *sep_a[3] = { "~{~^o~}~< ",
			  "'"         ,
			  "\""        
};
static char *sep_h[3] = { "~{~^h~}~< " ,
			  "~{~^m~}~< " ,
			  "~{~^s~}~< " 
};
static char *frm0 = "%d%s";
static char *frm1 = "%2.2d%s";
static char *stars= "***";

char *frm = frm0;
char **sep;
char *pt;

int ifield[10];
int nfield,first=(-1),last;
int curl;

strcpy(ostr,stars);

if(value<0.0) {
	*ostr = '-';
	pt = ostr+1;
	value=fabs(value);
} else
	pt = ostr;

if(*form != '%')
	return;
form++;

switch(TOLOWER(*form)) {		/* Select format of separator  */
case 'h':
	sep = sep_h;
	break;
case 'a':
	sep = sep_a;
	break;
default:
	return;
}

form++;

for(nfield=0;nfield<10;nfield++)
	ifield[nfield]=0;

if(TOLOWER(*form)=='u') {			/* put units into string      */
	if(first<0)
		first=0;
	last=0;
	MODF(ifield[0],value,1.0);
	form++;
}

if(TOLOWER(*form)=='m') {			/* put minutes into string   */
	if(first<0)
		first=1;
	last=1;
	MODF(ifield[1],value,60.0);
	form++;
}

if(TOLOWER(*form)=='s') {			/* put seconds into string   */
	if(first<0)
		first=2;
	last=2;
	MODF(ifield[2],value,60.0);
	form++;
}

if(first<0)
	return;

while(last>1 && last<9 && *form) {		/* put fractions             */
	last++;
	MODF(ifield[last],value,10.0);
	form++;
}

if(value>=0.5) 					/* Roundoff                 */
	ifield[last]++;

for(nfield=last; nfield>first; nfield--) 	/* Propagate                */
	if(nfield>2) {
		if(ifield[nfield]>9) {
			ifield[nfield] -= 10;
			ifield[nfield-1]++;
		}
	} else {
		if(ifield[nfield]>59) {
			ifield[nfield] -= 60;
			ifield[nfield-1]++;
		}
	}

if(first>0 && (ifield[first-1]>0)) { 		/* Overflow                 */
	strcpy(ostr,stars);
	return;					/* Return ***               */
}

frm = frm0;
curl = (last>2) ? 2 : last;

for(nfield=first; nfield<=curl; nfield++) { 	/* make the string          */
	sprintf(pt,frm,ifield[nfield],sep[nfield]);
	pt=strchr(pt,'\0');
	frm = frm1;
}
if(last>2) {
	strcat(ostr,"~<.");
	pt=strchr(pt,'\0');
	for(nfield=3; nfield<=last; nfield++) { 	/* make the string  */
		sprintf(pt,"%d",ifield[nfield]);
		pt=strchr(pt,'\0');
	}
}
}


/*****************************************************************************/
/*                                AGLExp                                     */

static void AGLExp(sel,value,string)
int sel;			/* either 'p' for power of ten or 'x' for    */
				/* power of e                                */
double value;			/* value to convert                          */
char *string;			/* output string                             */
{
int intval;
if(sel=='p') {
	value = log10(value);
	*string++ = '1';
	*string++ = '0';
} else {
	value = log(value);
	*string++ = 'e';
}

intval=AG_round(value);
sprintf(string,"~{~^%d~}",intval);
}


void AGLConvert(form,value,chstrg)	/* Convert a number into proper    */
					/* representation                  */
char *form;
double value;
char *chstrg;
{
char aux[20];
char lform[10];
char *src, *dst;
int  to_clean;
int  free=TRUE;

strcpy(chstrg,"****");

src=form;			/* Move precision fields into local string */
dst=lform;
*dst++ = *src++;
while(*src) {
	if(isalpha(*src))
		break;
	*dst++ = *src++;
	free=FALSE;
}
*dst='\0';

to_clean=(dst==(lform+1));

switch(*src)	{		/* src should point to format specifier    */
int ndigs;
case 'd':
case 'D':
	strcat(lform,"d");
	sprintf(chstrg,lform,(int)AG_round(value));
	break;
case 'e': 
case 'E':
	if(free)
		strcpy(lform,"%.4e");
	else
		strcat(lform,"e");
	sprintf(aux,lform,value);
	AGLCleanExp(aux,chstrg);
	break;
case 'g':
case 'G':
	if(value!=0.0)
		ndigs=log10(fabs(value))+1;
	else
		ndigs=1;
	if((ndigs<(-5))||(ndigs>5)) {
		strcpy(lform,"%.4e");
		sprintf(aux,lform,value);
		AGLCleanExp(aux,chstrg);
	} else {
		ndigs = -(ndigs-5);
		ndigs = (ndigs<1) ? 1 : ndigs;
		sprintf(lform,"%%.%df",ndigs);
		sprintf(chstrg,lform,value);
		AGLCleanFloat(chstrg,chstrg,TRUE);
	}
	break;
case 'f':
case 'F':
default:
	if(free) {
		if(value!=0.0)
			ndigs=log10(fabs(value))+1;
		else
			ndigs=1;
		ndigs = -(ndigs-5);
		ndigs = (ndigs<1) ? 1 : ndigs;
		sprintf(lform,"%%.%df",ndigs);
		sprintf(chstrg,lform,value);
	} else
		strcat(lform,"f");
	sprintf(chstrg,lform,value);
	if(to_clean)
		AGLCleanFloat(chstrg,chstrg,FALSE);
	break;
case 'p':
case 'P':
	AGLExp('p',value,chstrg);
	break;
case 'x':
case 'X':
	AGLExp('x',value,chstrg);
	break;
case 'a':
case 'A':
case 'h':
case 'H':
case 'm':
case 'M':
case 's':
case 'S':
	strcat(lform,src);
	AGLSex(chstrg,lform,value);
	break;
}
}


void AGLsset(cmd,mmode)		/* Set mode with metafile control   */
char *cmd;
int mmode;
{
if(mmode==2)
	AG_MSUS();
AG_SSET(cmd);

if(mmode==2)
	AG_MRES();
}


double AGLnmjt(ctik,logflg,step)	/* Get nex major tik position        */
double ctik;				/* Current tik position              */
int logflg;				/* Axis mode                         */
double step;				/* Tik distance                      */
{
if(logflg)
	return (ctik * step);
else 
	return (ctik + step);
}

#define GUARD_VALUE 0.0005

double AGLGuardL(stop,logflg,step)	/* Provides a value which is suitably*/
					/* smaller  then stop to check for   */
					/* last tick taking rounding errors  */
					/* into account                      */
double stop;
int logflg;
double step;
{
if(logflg)
	return stop/pow(step,GUARD_VALUE);
else
	return stop-step*GUARD_VALUE;
}

double AGLGuardU(stop,logflg,step)	/* Provides a value which is suitably*/
					/* greather then stop to check for   */
					/* last tick taking rounding errors  */
					/* into account                      */
double stop;
int logflg;
double step;
{
if(logflg)
	return stop*pow(step,GUARD_VALUE);
else
	return stop+step*GUARD_VALUE;
}


double AGLnmnt(ctik,logflg,divs,mjtk) 	/* Get nex minor tik position        */
double ctik;				/* Current tik position              */
int logflg;				/* Axis mode                         */
double divs;				/* Tik distance (<0: backwards)      */
double mjtk;				/* Major division distance           */
{
if(logflg) {
	if(divs>0.0) {
		if((divs>0.1)&&(divs<10.0))  
			return (ctik+(divs*mjtk));
		else
			return (ctik*divs);
	} else {
		divs=fabs(divs);
		if((divs>0.1)&&(divs<10.0))  
			return (ctik-(divs*(mjtk*0.1)));
		else
			return (ctik/divs);
	}
} else
	return (ctik+divs);
}


