/*  Planets
 *           A program to determine the position of
 *	     the Sun., Mer., Ven., Mars, Jup, Sat & Ura
 *
 *  reference Jean Meeus's book, " Astronomical Formulae for
 *  Calculators
 *			program by
 *
 *			F.T. Mendenhall
 *			ihnp4!inuxe!fred
 *
 *		Copyright 1985 by F.T. Mendenhall
 *		all rights reserved
 *
 * (This copyright prevents you from selling the program - the
 *  author grants anyone the right to copy and install the program
 *  on any machine it will run on)
 !
 ! Modified by Alan Paeth, awpaeth@watcgl, January 1987 for
 !   (1) non-interactive mode (uses current GMT)
 !   (2) output in simplified Yale format for use with star charter software
 !   (3) lines 365-380 rewritten as a simplier C expression (awpaeth, Apr '87)
 !
 ! Modified by Petri Launiainen, pl@intrin.FI, February 1987 for
 !   easier LOGFILE definition/modification in Makefile
 !
 ! Modified by Alan Paeth, September 1987, for command line flags.
 !    The program continues to provide reduced Yale output in the old style,
 !     but new versions of "starchart" accept either. The old format allows
 !     representation of magnitudes below -.99 (useful for planets), but no
 !     spectral or stellar identification, which is not useful for planets.
 !
 ! Modified by Jim Cobb, March 1988, to compute moon positions as well.
 !    Also, to  change slightly the computation of epli, the obliquity
 !     of the ecliptic.  Formerly epli had a term 
 !		+0.00256*cos(omeg*rad).
 !     This term is a compensation for computing the apparent position
 !     of the sun (Chapter 18 in Meeus).  But for converting
 !     ecliptical coordinates to right ascension and declination we want
 !     the mean value (that is, we don't want this term).
 ! 
 ! Modified by Bob Leivian, Sept 1989, to compute local azimuth and altitude
 !     given a lat/lon, also fixed time so you can replace a single item
 !     without having to supply all (i.e. for today at 9:00 put -t 9) 
 !     also ran the code through 'cb' to standardize the indention.
 !     I split the code in to more managible pieces.
 !     Also printed out time in local and UTC as well as JD
 !
 ! See "Time Notes" section below if you plan to rewrite the user interface.
 */

#include <stdio.h>
#include <math.h>

/* default latitude longitude (of Tempe, AZ) -- replace with your favorite place */
#define DEFLONG -111.94
#define DEFLAT 33.32
#define DEFZONE 7 /* MST */

#ifndef PLANETFILE
#define PLANETFILE "./planet.star"
#endif

double	pie, rad;
double	htod(), atof(), kepler(), truean();
double	longi(), lati(), poly(), to_int(), range();
int	cls(), helio_trans(), geo_trans(), speak();

double	planet_pos();
double	sidereal_time();
int	zone;
double	longitude = DEFLONG;
double	latitude = DEFLAT;
double	GHA_Aries;


FILE    *logfile;
char	*progname;

/*
 * Times Notes.
 * 
 * This software would ideally use a generic UNIX system call to which would
 * provide day and date info as integers, plus current time west of GMT.
 * These values would become the default settings for each of the -m -d -y -t
 * & -z. Unfortunately, time on across many UNIX systems does not (to my
 * knowledge) exist as a uniform subroutine call.
 * 
 * Instead, we presume that the very low level "gettimeofday" (GMT in seconds,
 * from 1 Jan 1970) exists on all UNIX installations, and use this subroutine
 * to use the present time of day when no command line parameters are
 * present, as defaults for the current time have not been filled in.
 * 
 * When any flag-style command line parameters are present, hardcoded defaults
 * are substitude for -m, -d, -y -t switches, which may then be overridden.
 * The -z flag is filled in by a companion return value from "gettimeofday".
 * This approach makes for poor (on account of hard coding) defaults.
 * 
 * A worthwhile change would be to simplify the command line control structure
 * and default mechanism by using a higher level system call, or (safer),
 * write the routine to convert Unix GMT into day, month, year, so that it
 * lives within this program. This would allow for more reasonable defaults,
 * and still merely one UNIX system call to get GMT time. Perhaps sources
 * from a UNIX system may be studied to do this correctly.
 * 
 * Expertise in "time" on a specific UNIX system is *NOT* what is required --
 * what *IS* required is knowledge and access to enough independent UNIX
 * systems to insure (to a high probability) that the code remains portable.
 * In other words, only low-level system calls known to work across a large
 * range of systems should be employed.
 * 
 */

#include <sys/time.h>		/* for getting current GMT */

double
current_time(m, d, y, t, z)
int	*m;
int	*d;
int	*y;
double	*t;
int	*z;
{
#define SECSPERDAY (60.0 * 60.0 * 24.0)

#ifdef UNIX
#define GMT1970 2440587.5
	struct timeval tv;
	struct timezone tz;
	struct tm *p;

	gettimeofday(&tv, &tz);
	p = localtime(&tv);

	*y = p->tm_year + 1900;			/* local year */
	*m = p->tm_mon + 1;				/* local month */
	*d = p->tm_mday;				/* local day of month */

	*t = p->tm_hour + (p->tm_min / 60.0);	/* local time (0.0-23.999) */
	*z = tz.tz_minuteswest;	    /* local time hrs west of Greenwich */

	return (GMT1970 + tv.tv_sec / SECSPERDAY);
#endif 

#ifdef AMIGA
	time_t amiga_now;
	struct tm *p;

#define GMT1978 2443509.5
	amiga_now = time(&amiga_now);

    p = localtime(&amiga_now);

	*t = p->tm_hour + (p->tm_min / 60.0);	/* local time (0.0-23.999) */
	*z = DEFZONE * 60;
    *y = p->tm_year + 1900;			/* local year */
    *m = p->tm_mon + 1;				/* local month */
    *d = p->tm_mday;				/* local day of month */
    return GMT1978 + (amiga_now / SECSPERDAY) + (DEFZONE / 24.0);
#endif

#ifdef NO_TIME_AVAIL
#define MST1989 2447528.291667
	/* no time is avail, pick a default i.e. Noon Jan 1 1989 */
	*y = 1989;
	*m = 1;
	*d = 1;

	*t = 12.0;
	*z = DEFZONE * 60.;
	return MST1989;
#endif
}

double
commandtime(argc, argv)
char	**argv;
{
	int	mm, dd, yy, aa1, bb1;
	double	jd, year, month, day, time, timez;
	int	j;
	double	now;
	static char	*usage = 
"\nusage:planet {current time used}\nor\tplanet juliandate\nor\tplanet [-t hh.mm -z zone -y year -m mon -d day ]";

	/* get current time for defaults */
	now = current_time(&mm, &dd, &yy, &time, &zone);
	timez = zone / 60.; /* time zone in hours */

	/* no args - use current time */
	if (argc == 1)
		return now;

	if ((argv[1][0] != '-')) {
		/* one numeric arg - use it as the julian date */
		if (argc == 2)
			return (atof(argv[1]));
		die("no switches - %s", usage);
	} else {
		/* flags present, set up defaults */

		/* process any user overrides */
		for (j = 1; j < argc; j++) {
			if (argv[j][0] != '-')
				die("unknown argument - %s", usage /*argv[j]*/);
			switch (argv[j][1]) {
			case 't': 
				time = htod(argv[++j]); 
				break;
			case 'z': 
				timez = htod(argv[++j]);
				if (timez < 0 || timez > 24)
					die("time zone must be 0 to 24 west of UTC\n");
                                zone = timez * 60.;
				break;
			case 'y': 
				yy = atoi(argv[++j]); 
				break;
			case 'm': 
				mm = atoi(argv[++j]);
				if (mm < 1 || mm > 12)
					die("month must be 1..12\n");
				break;
			case 'd': 
				dd = atoi(argv[++j]);
				if (dd < 1 || dd > 31)
					die("day must be 1..31\n");
				break;
			case 'l':
				switch (argv[j][2]) {
				case 'o':
					longitude = atof(argv[++j]);
					if (longitude < -180 || longitude > 180)
						die("longitude must be +-180\n");
					break;
				case 'a':
					latitude = atof(argv[++j]);
					if (latitude < -90 || latitude > 90)
						die("latitude must be +-90\n");
					break;
				default:
					die("specify lat or lon\n");
				}
				break;
			default:
				die("unknown switch - %s", usage /*argv[j]*/ );
			}
			if (j == argc)
				die("trailing command line flag - %s", argv[j - 1]);
		}
	}
	day = ((float) dd + (time + timez) / 24.0);
	if (mm > 2) {
		year = yy;
		month = mm;
	} else {
		year = yy - 1;
		month = mm + 12;
	}
	aa1 = (int) (year / 100);
	bb1 = 2 - aa1 + (int) (aa1 / 4);
	jd = to_int(365.25 * year) + to_int(30.6001 * (month + 1));
	jd = jd + day + 1720994.5;
	if ((year + month / 100) > 1582.10)
		jd = jd + bb1;
	return (jd);
}


caltim(jd_frac, hour, min)
double	jd_frac;
int	*hour;
int	*min;
{
	double	x;

	x = jd_frac * 24.0;
	*hour = (int) floor(x);
	x -= floor(x);
	x *= 60.0;
	*min = (int) floor(x);
}


main(argc, argv)
int	argc;
char	**argv;
{
	double	jd, T0, epli;
	double	temp;
        long    int_jd;
	int	y, m, d, h, min, lh, lmin;
	char	*str;

	progname = argv[0];
#ifdef AMIGA
	logfile = 0; /* don't write a log */
#else
	if ((logfile = fopen(PLANETFILE, "w")) == 0)
		/*if ((logfile = fopen("/tmp/planet.star", "w")) == 0) {
        	
			die("fail on opening logfile %s\n", "/tmp/planet.star");
		}*/
#endif

	cls();

	pie = 3.1415926535898;
	rad = pie / 180.0;

	/*  calculate time T0 from 1900 January 0.5 ET */
	jd = commandtime(argc, argv);

	/* jd to calendar day */
        int_jd = jd +.5001;
	caldat(int_jd, &m, &d, &y);

	/* julian day to UTC */
	temp = jd - .4999;
	caltim(temp - floor(temp), &h, &min);
	printf("At %d/%d %d %d:%02d UTC (Julian: %.3f)\n", m, d, y, h, min, jd);

	/* UTC to Local */
	temp -= (zone / 1440.);
	caltim(temp - floor(temp), &lh, &lmin);
        int_jd = jd + .5001 - (zone / 1440.);
	caldat(int_jd, &m, &d, &y);
	if (lh > 12) {
		lh -= 12;
		str = "pm";
	} else {
		str = "am";
		if (lh == 0)
			lh = 12;
	}
	printf("local %d/%d %d:%02d%s (", m, d, lh, lmin, str);

	/* compute exact local time for a given longitude */
	temp = jd - .5 + (longitude / 360.);
	caltim(temp - floor(temp), &lh, &lmin);
	GHA_Aries =  15. * sidereal_time(jd);

	printf("%2d:%02d true local at lat:%1.1f lon:%1.1f, GHA Aries:%1.1f)\n",
	    lh, lmin, latitude, longitude, GHA_Aries);

	T0 = (jd - 2415020.0) / 36525.0;
	printf("\nOBJECT      R.A.              DEC          DISTANCE   altitude   azimuth\n");

	epli = planet_pos(jd, T0);

	moon_pos(T0, epli);

	putchar('\n');
	if (logfile)
		close(logfile);
        logfile = 0;

    /* show local position of some stars also */
	speak(101.1714, -16.7013, 0., -1.46, " " , "Sirius");
	speak(213.7956, 19.236, 0., -0.04, " " , "Arcturus");
	speak(88.6508, 7.4057, 0., 0.5, " " , "Betelgeuse");
	exit(0);
}				/* end of program main */


int
cls()
{
#define CLEARCNT 1
	int	i;
	for (i = 0; i < CLEARCNT; i++)
		putchar('\n');
}


double
poly(a0, a1, a2, a3, t)
double	a0, a1, a2, a3, t;
{
	return (a0 + a1 * t + a2 * t * t + a3 * t * t * t);
}


double
to_int(z)
double	z;
{
	long	trunk;

	trunk = (long) z;
	z = (double) trunk;
	return (z);
}


double
kepler(e, M)
double	e, M;
{
	double	corr, e0, E0, E1;
	e0 = e / rad;
	corr = 1;
	E0 = M;
	while (corr > 0.000001) {
		corr = (M + e0 * sin(E0 * rad) - E0) / (1 - e * cos(E0 * rad));
		E1 = E0 + corr;
		if (corr < 0) {
			corr = -1.0 * corr;
		}
		E0 = E1;
	}

	return (E1);
}


double
truean(e, E)
double	e, E;
{
	double	nu;

	nu = 2.0 * atan(sqrt((1 + e) / (1 - e)) * tan(E * rad / 2));
	nu = nu / rad;
	if (nu < 0.0) {
		nu = nu + 360.0;
	}
	if (fabs(nu - E) > 90.0) {
		if (nu > 180.0) {
			nu = nu - 180.0;
		} else {
			nu = nu + 180.0;
		}
	}
	return (nu);
}


double
longi(w2, i, u)
double	w2, i, u;
{
	double	x, y, l;

	y = cos(i * rad) * sin(u * rad);
	x = cos(u * rad);
	l = atan2(y, x);
	l = l / rad;
	if (l < 0.0) {
		l = l + 360.0;
	}
	return (l + w2);
}


double
lati(u, i)
double	u, i;
{
	double	b;
	b = asin(sin(u * rad) * sin(i * rad)) / rad;
	return (b);
}


int
speak(ra, dec, dis, mag, sym, str)
double	ra, dec, dis, mag;
char	*sym, *str;
{
	int	h, m, s, deg;
	double	lha, sha, altitude, azimuth;
	;

	if (ra < 0) {
		ra = ra + 360.0;
	}
	sha = 360. - ra;
	ra = ra / 15.0;		/* convert from degs to hours */
	h = (int) to_int(ra);
	m = (int) ((ra - h) * 60);
	s = (int) (((ra - h) * 60 - m) * 60);
	printf("%-12s%2dh %2dm %2ds ", str, h, m, s);
	if (logfile) 
		fprintf(logfile, "%02d%02d%02d", h, m, s);
	deg = (int) to_int(dec);
	m = (int) ((dec - deg) * 60);
	s = (int) (((dec - deg) * 60 - m) * 60);
	if (m < 0) {
		m = m * -1;
	}
	if (s < 0) {
		s = s * -1;
	}
#ifdef AMIGA
	/* its printf doesn't know about signed */
	if (deg > 0)
		printf("   +%2ddeg %2dm %2ds ", deg, m, s);
	else
		printf("   %3ddeg %2dm %2ds ", deg, m, s);
#else
	printf("   %+3ddeg %2dm %2ds ", deg, m, s);
#endif

	if (dis > 0.0001)
		printf("  %6.3f", dis);
	else
		printf("        ");
	if (logfile)
		if (mag < 0.0)
			fprintf(logfile, "%+03d%02d-%2d%s       %s\n",
			    deg, m, -(int) (10 * mag), sym, str);
		else
			fprintf(logfile, "%+03d%02d%3d%s       %s\n",
			    deg, m, (int) (100 * mag), sym, str);

	/* if we have a valid angle */
	if (GHA_Aries > 0) {
		/* now tell where it is at a given place on earth */
		lha = range(GHA_Aries + sha + longitude);

		altitude = sin(latitude * rad) * sin(dec * rad) + 
		    cos(latitude * rad) * cos(dec * rad) * cos(lha * rad);
		altitude = asin(altitude) / rad;

		azimuth = sin(lha * rad) / 
		    (cos(lha * rad) * sin(latitude * rad) - 
		    tan(dec * rad) * cos(latitude * rad));

		/* correct for quadrant */
		if (lha <= 180.) {
			if (azimuth > 0)
				azimuth = 180. + atan(azimuth) / rad;
			else
				azimuth = 360. + atan(azimuth) / rad;
		} else {
			if (azimuth <= 0.)
				azimuth = 180. + atan(azimuth) / rad;
			else
				azimuth = atan(azimuth) / rad;
		}

		printf("      %5.1f     %5.1f\n", altitude, azimuth);
	} else
		printf("\n");
	return (0);
}


double
range(val)
double	val;
{
	while (val < 0.0) {
		val = val + 360.0;
	}
	if (val > 360.0) {
		val = val - (to_int(val / 360.0) * 360.0);
	}
	return (val);
}


/*
 * Helio_trans converts heliocentric ecliptical coordinates to geocentric
 * right ascension and declination.  It also outputs the converted value.
 */
int
helio_trans(r, b, ll, Stheta, Sr, epli, mag, sym, str)
double	r, b, ll, Stheta, Sr, epli, mag;
char	*sym, *str;
{
	double	N, D, lambda, delta, beta;

	N = r * cos(b * rad) * sin((ll - Stheta) * rad);
	D = r * cos(b * rad) * cos((ll - Stheta) * rad) + Sr;
	lambda = atan2(N, D) / rad;
	if (lambda < 0.0) {
		lambda = lambda + 360.0;
	}
	lambda = range(lambda + Stheta);
	delta = sqrt(N * N + D * D + (r * sin(b * rad)) * (r * sin(b * rad)));
	beta = asin(r * sin(b * rad) / delta) / rad;
	geo_trans(lambda, beta, epli, delta, mag, sym, str);
	return (0);
}


/*
 * Geo_trans converts geocentric ecliptical coordinates to geocentric right
 * ascension and declination.  It also outputs the converted value.  Note
 * that the output coordinates are referred to the mean equinox of date.  If
 * these coordinates are to be used in conjunction with a star database, use
 * the epoch program to convert the planet's coordinates to the epoch for the
 * star database.
 */
int
geo_trans(lambda, beta, epli, delta, mag, sym, str)
double	lambda, beta, epli, delta, mag;
char	*sym, *str;
{
	double	N, D, RA, DEC;

	N = sin(lambda * rad) * cos(epli * rad)
	-tan(beta * rad) * sin(epli * rad);
	D = cos(lambda * rad);
	RA = atan2(N, D) / rad;
	DEC = asin(sin(beta * rad) * cos(epli * rad)
	     + cos(beta * rad) * sin(epli * rad) * sin(lambda * rad)) / rad;
	speak(RA, DEC, delta, mag, sym, str);
	return (0);
}


double
htod(s)
char	*s;
{
	/*
     * htod(str) reads floating point strings of the form {+-}hh.{mm} thus
     * allowing for fractional values in base 60 (ie, degrees/minutes).
     */
	double	x, sign;
	int	full, frac;
	char	*t;
	t = s - 1;
	while (*++t) {
		if (((*t < '0') || (*t > '9')) && (*t != '.') && (*t != '+') && (*t != '-'))
			die("non-digit in dd.mm style numeric argument");
	}
	if (s == NULL)
		return 0.0;
	full = frac = 0;
	sign = 1.0;
	if (*s == '-') {
		sign = -1.0;
		s++;
	} else if (*s == '+')
		s++;
	while (*s && *s != '.')
		full = 10 * full + *s++ - '0';
	if (*s++ == '.') {
		if (*s)
			frac = 10 * (*s++ - '0');
		if (*s)
			frac += *s++ - '0';
		if (frac > 59)
			frac = 59;
	}
	x = (double) full + ((double) frac) / 60.0;
	return sign * x;
}


die(a, b)
char	*a, *b;
{
	fprintf(stderr, "%s: ", progname);
	fprintf(stderr, a, b);
	fprintf(stderr, "\n");
	fflush(stderr);
	exit(1);
}
