/***************************************************************************\
*          PREDICT: A satellite tracking/orbital prediction program         *
*          Project started 26-May-1991 by John A. Magliacane, KD2BD         *
*                        Last update: 14-July-2026                          *
*****************************************************************************
*                                                                           *
* 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 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.                                  *
*                                                                           *
*****************************************************************************
*          See the "CREDITS" file for the names of those who have           *
*   generously contributed their time, talent, and effort to this project.  *
\***************************************************************************/

#include <math.h>
#include <time.h>
#include <sys/time.h>
#include <curses.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <pthread.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <curl/curl.h>

#if defined __has_include
  #if __has_include(<alsa/asoundlib.h>)
	#include <alsa/asoundlib.h>
	#define SOUNDCARD 1
  #elif defined (__ANDROID__)
	char wavestring[1024];
	#define SOUNDCARD 1
  #else
	#define SOUNDCARD 0 	
  #endif
#endif

/* Constants used by SGP4/SDP4 code */

#define	km2mi		0.621371		/* km to miles */
#define deg2rad		1.745329251994330E-2	/* Degrees to radians */
#define pi		3.14159265358979323846	/* Pi */
#define pio2		1.57079632679489656	/* Pi/2 */
#define x3pio2		4.71238898038468967	/* 3*Pi/2 */
#define twopi		6.28318530717958623	/* 2*Pi  */
#define e6a		1.0E-6
#define tothrd		6.6666666666666666E-1	/* 2/3 */
#define xj2		1.0826158E-3		/* J2 Harmonic (WGS '72) */
#define xj3		-2.53881E-6		/* J3 Harmonic (WGS '72) */   
#define xj4		-1.65597E-6		/* J4 Harmonic (WGS '72) */
#define xke		7.43669161E-2
#define xkmper		6.378137E3		/* WGS 84 Earth radius km */
#define xmnpda		1.44E3			/* Minutes per day */
#define ae		1.0
#define ck2		5.413079E-4
#define ck4		6.209887E-7
#define f		3.35281066474748E-3	/* Flattening factor */
#define ge		3.986008E5 	/* Earth gravitational constant (WGS '72) */
#define s		1.012229
#define qoms2t		1.880279E-09
#define secday		8.6400E4	/* Seconds per day */
#define omega_E		1.00273790934	/* Earth rotations/siderial day */
#define omega_ER	6.3003879	/* Earth rotations, rads/siderial day */
#define zns		1.19459E-5
#define c1ss		2.9864797E-6
#define zes		1.675E-2
#define znl		1.5835218E-4
#define c1l		4.7968065E-7
#define zel		5.490E-2
#define zcosis		9.1744867E-1
#define zsinis		3.9785416E-1
#define zsings		-9.8088458E-1
#define zcosgs		1.945905E-1
#define zcoshs		1
#define zsinhs		0
#define q22		1.7891679E-6
#define q31		2.1460748E-6
#define q33		2.2123015E-7
#define g22		5.7686396
#define g32		9.5240898E-1
#define g44		1.8014998
#define g52		1.0508330
#define g54		4.4108898
#define root22		1.7891679E-6
#define root32		3.7393792E-7
#define root44		7.3636953E-9
#define root52		1.1428639E-7
#define root54		2.1765803E-9
#define thdt		4.3752691E-3
#define rho		1.5696615E-1
#define mfactor		7.292115E-5
#define sr		6.96000E5	/* Solar radius - km (IAU 76) */
#define AU		1.49597870691E8	/* Astronomical unit - km (IAU 76) */

#define ISS_EL_LIMIT	-6.0		/* ISS elevation visibility limit */
#define ALSA_PCM_NEW_HW_PARAMS_API	/* For "Vocalizer" code */
#define mean_solar_year	365.24219870

/* Entry points of Deep() */

#define dpinit   1 /* Deep-space initialization code */
#define dpsec    2 /* Deep-space secular code        */
#define dpper    3 /* Deep-space periodic code       */

/* Flow control flag definitions */

#define ALL_FLAGS              -1
#define SGP_INITIALIZED_FLAG   0x000001	/* not used */
#define SGP4_INITIALIZED_FLAG  0x000002
#define SDP4_INITIALIZED_FLAG  0x000004
#define SGP8_INITIALIZED_FLAG  0x000008	/* not used */
#define SDP8_INITIALIZED_FLAG  0x000010	/* not used */
#define SIMPLE_FLAG            0x000020
#define DEEP_SPACE_EPHEM_FLAG  0x000040
#define LUNAR_TERMS_DONE_FLAG  0x000080
#define NEW_EPHEMERIS_FLAG     0x000100	/* not used */
#define DO_LOOP_FLAG           0x000200
#define RESONANCE_FLAG         0x000400
#define SYNCHRONOUS_FLAG       0x000800
#define EPOCH_RESTART_FLAG     0x001000
#define VISIBLE_FLAG           0x002000
#define SAT_ECLIPSED_FLAG      0x004000

struct	{  char text[255];
	   char name[30];
	   unsigned catnum;
	   unsigned setnum;
	   char designator[15];
 	   unsigned year;
	   double refepoch;
	   double incl;
	   double raan;
	   double eccn;
	   double argper;
	   double meanan;
	   double meanmo;
	   double drag;
	   double nddot6;
  	   double bstar;
	   unsigned orbitnum;
	}  sat[24];

struct	{  char callsign[17];
	   double stnlat;
	   double stnlong;
	   int stnalt;
	}  qth;

struct	{  char name[30];
	   unsigned catnum;
	   char squintflag;
	   double alat;
	   double alon;
	   unsigned char transponders;
	   char transponder_name[10][80];
	   double uplink_start[10];
	   double uplink_end[10];
	   double downlink_start[10];
	   double downlink_end[10];
	   unsigned char dayofweek[10];
	   int phase_start[10];
	   int phase_end[10];
	   char need_to_save;
	}  sat_xponder[24];

/* Global variables for sharing data among functions... */

double	tsince, jul_epoch, jul_utc, eclipse_depth=0,
	sat_azi, sat_ele, sat_range, sat_range_rate,
	sat_lat, sat_lon, sat_alt, sat_vel, phase,
	sun_azi, sun_ele, daynum, fm, fk, age, aostime,
	lostime, ax, ay, az, rx, ry, rz, squint, alat, alon,
	sun_ra, sun_dec, sun_lat, sun_lon, sun_range, sun_range_rate,
	moon_az, moon_el, moon_dx, moon_ra, moon_dec, moon_gha, moon_dv;

char	qthfile[100], kepfile[100], output[25], temp[255],
	rotctld_host[256]={0}, rotctld_port[6], resave=0, force=0,
	reload_kep=0, netport[10], once_per_second=0, ephem[5],
	sat_sun_status, findsun, calc_squint, database, xterm,
	io_lat='N', io_lon='W', android=0, *day[]={"Any","Mon","Tue",
	"Wed","Thu","Fri","Sat","Sun"}, *numstr[]={"zero","one","two",
	"three","four","five","six","seven","eight","nine","ten",
	"eleven","twelve","thirteen", "fourteen","fifteen","sixteen",
	"seventeen","eighteen", "nineteen"}, awake=0, talking=0;

int	indx, iaz, iel, ma256, isplat, isplong, socket_flag=0,
	Flags=0, rotctld_socket;

long	rv, irk;

/* Mouse tools */

mmask_t bstate;
MEVENT	event;

unsigned char val[256], buffer[65536];

/* The following variables are used by the socket server.  They
   are updated in the MultiTrack() and SingleTrack() functions. */

char	visibility_array[24], tracking_mode[30];

float	az_array[24], el_array[24], long_array[24], lat_array[24],
	footprint_array[24], range_array[24], altitude_array[24],
	velocity_array[24], eclipse_depth_array[24], phase_array[24],
	squint_array[24];

double	doppler[24], nextevent[24];

long	aos_array[24];
unsigned orbitnum_array[24];

unsigned short portbase=0;

/** Type definitions **/

/* Keplerian orbital data structure used directly by the SGP4/SDP4 code. */

typedef struct  {
		   double  day, year, xndt2o, xndd6o, bstar,
			   xincl, xnodeo, eo, omegao, xmo, xno;
		   int	   catnr, elset, revnum;
		   char	   sat_name[25], idesg[9];
		}  tle_t;

/* Geodetic position structure used by SGP4/SDP4 code. */

typedef struct	{
		   double lat, lon, alt, theta;
		}  geodetic_t;

/* General three-dimensional vector structure used by SGP4/SDP4 code. */

typedef struct	{
		   double x, y, z, w;
		}  vector_t;

/* Common arguments between deep-space functions used by SGP4/SDP4 code. */

typedef struct	{
		   	   /* Used by dpinit part of Deep() */
		   double  eosq, sinio, cosio, betao, aodp, theta2,
			   sing, cosg, betao2, xmdot, omgdot, xnodot, xnodp;
	   
			   /* Used by dpsec and dpper parts of Deep() */
		   double  xll, omgadf, xnode, em, xinc, xn, t;
    
		 	   /* Used by thetg and Deep() */
		   double  ds50;
		}  deep_arg_t;

/* Global structure used by SGP4/SDP4 code. */

geodetic_t obs_geodetic;

/* Two-line Orbital Elements for the satellite used by SGP4/SDP4 code. */

tle_t tle;

/* "Vocalizer" functions for Linux/Unix */

#if defined __has_include
  #if __has_include(<alsa/asoundlib.h>)

unsigned long buffer2long(int indx)
{
	unsigned long byte0, byte1, byte2, byte3;

	if ((indx+3)<65536)
	{
		byte0=(unsigned long)buffer[indx];
		byte1=(unsigned long)buffer[indx+1];
		byte2=(unsigned long)buffer[indx+2];
		byte3=(unsigned long)buffer[indx+3];

		return (byte0|(byte1<<8)|(byte2<<16)|(byte3<<24));
	}

	else
		return 0L;
}

unsigned int buffer2int(int indx)
{
	unsigned int byte0, byte1;

	if ((indx+1)<65536)
	{
		byte0=(unsigned int)buffer[indx];
		byte1=(unsigned int)buffer[indx+1];

		return (byte0|(byte1<<8));
	}
	else
		return 0;
}

int wavplay(char *filename)
{
	int x, y, format, bytes, channels, rc, fd;
	unsigned long total_samples=0L, running_total=0L;
	unsigned int rate=0;
	char filenpath[255];
	snd_pcm_t *handle;
	snd_pcm_hw_params_t *params;

	// snprintf(filenpath,255,"%svocalizer/%s.wav",predictpath,filename);
	snprintf(filenpath,255,"%svocalizer/%s.wav",BUILD_DIR,filename);

	fd=open(filenpath,O_RDONLY);

	if (fd==-1)
		return -1;

	bytes=read(fd,&buffer,65536);

	y=((buffer[0]^'R')  | (buffer[1]^'I') |
  	   (buffer[2]^'F')  | (buffer[3]^'F') |
	   (buffer[8]^'W')  | (buffer[9]^'A') |
	   (buffer[10]^'V') | (buffer[11]^'E'));

	if (y)
	{
		close(fd);
		return -1;
	}

	for (x=12, y=1; x<65535 && y; x++)
		y=((buffer[x]^'f')  | (buffer[x+1]^'m') |
		  (buffer[x+2]^'t') | (buffer[x+3]^' '));

	if (y==0)
	{
		x+=3;
		x+=4;
		format=buffer2int(x);
		x+=2;
		channels=buffer2int(x);
		x+=2;
		rate=buffer2long(x);
		x+=4;
		x+=4;
		buffer2int(x);
		x+=2;
		buffer2int(x);  /* bits = */
		x+=2;

		if (format!=1)
			return -1;

		for (x=0, y=1; x<65535 && y; x++)
			y=((buffer[x]^'d') | (buffer[x+1]^'a') |
			 (buffer[x+2]^'t') | (buffer[x+3]^'a'));

		if (y==0)
		{
			x+=3;
			total_samples=buffer2long(x);

			if (total_samples!=0L)
			{
				/* Open PCM device for playback. */
				rc=snd_pcm_open(&handle, "default", SND_PCM_STREAM_PLAYBACK, 0);

				/* Allocate a hardware parameters object. */
				snd_pcm_hw_params_alloca(&params);

				/* Fill it in with default values. */
				snd_pcm_hw_params_any(handle, params);

				/* Set the desired hardware parameters. */

				/* Interleaved mode */
				snd_pcm_hw_params_set_access(handle, params, SND_PCM_ACCESS_RW_INTERLEAVED);

				/* Signed 16-bit little-endian format */
				snd_pcm_hw_params_set_format(handle, params, SND_PCM_FORMAT_S16_LE);

				/* Set the number of audio channels */
				snd_pcm_hw_params_set_channels(handle, params, channels);

				/* Set the sampling rate (in Hz) */
				snd_pcm_hw_params_set_rate_near(handle, params, &rate, 0);

				/* Set the buffer size */
				snd_pcm_hw_params_set_buffer_size(handle, params, 65536);

				/* Write the parameters to the driver */
				rc=snd_pcm_hw_params(handle, params);

				if (rc<0)
					return -1;

				lseek(fd,(long)(x+4),SEEK_SET);
				bytes=read(fd,&buffer,65536);
				running_total+=bytes;

				if (running_total>total_samples)
					bytes-=(running_total-total_samples);

				while (bytes>0)
				{
					rc=snd_pcm_writei(handle, buffer, ((bytes/2)/channels));

					bytes=read(fd,&buffer,65536);
					running_total+=bytes;

					if (running_total>total_samples)
						bytes-=(running_total-total_samples);
				}

				snd_pcm_drain(handle);
				snd_pcm_close(handle);
				close(fd);
			}
		}
	}

	return 0;
}

void saynumber(int num)
{
	char string[10];

	sprintf(string,"%4d",num);

	if (string[1]!=32)
	{
		wavplay(numstr[string[1]-'0']);
		wavplay("hundred");
	}

	switch (string[2])
	{
		case '1':
			wavplay(numstr[(10+(string[3]-'0'))]);
			break;

		case '2':	
			wavplay("twenty");
			break;

		case '3':
			wavplay("thirty");
			break;

		case '4':
			wavplay("forty");
			break;

		case '5':
			wavplay("fifty");
			break;

		case '6':
			wavplay("sixty");
			break;

		case '7':
			wavplay("seventy");
			break;

		case '8':
			wavplay("eighty");
			break;

		case '9':
			wavplay("ninety");
			break;
	}

	if (string[3]!=32 && string[3]!=0 && string[2]!='1' && string[3]!='0')
		wavplay(numstr[string[3]-'0']);

	if (string[3]=='0' && string[2]==32 && string[1]==32)
		wavplay("zero"); 
}

void *vocalizer(void *arg)
{
	char *string=(char *)arg;
	char str1[20], str2[20], str3[20], str4[20], str5[20];
	int argnum=0, number;

	talking=1;

	argnum=sscanf(string,"%s%s%s%s%s",str1,str2,str3,str4,str5);

	if (argnum==1)
	{
		if (strncmp(str1,"eclipse",7)==0)
		{
			wavplay("alarm");
			wavplay("eclipse");
		}

		if (strncmp(str1,"sunlight",8)==0)
		{
			wavplay("alarm");
			wavplay("sunlight");
		}

		if (strncmp(str1,"los",3)==0)
			wavplay("los");
	}

	if (argnum>=2)
	{
		wavplay("intro");

		number=iaz;
		saynumber(number);
		wavplay("azimuth");

		if (sat_range_rate>0.0)
			number=floor(sat_ele);
		else
			number=ceil(sat_ele);

		if (sat_ele<0.5)
			number=0;
 
		saynumber(number);
		wavplay("elevation");

		if (argnum>=3)
		{
			if (str3[0]=='+')
				wavplay("approaching");

			if (str3[0]=='-')
				wavplay("receding");
		}

		if (argnum==4)
		{
			if (str4[0]=='V')
				wavplay("visible");
		}
	}

	talking=0;

	return NULL;
}

#elif defined (__ANDROID__)

/* "Vocalizer" functions for Termux/Android */

int stringBuilder(char *filename)
{
	strncat(wavestring,BUILD_DIR,100);
	strncat(wavestring,"vocalizer/",11);
	strncat(wavestring,filename,20);
	strncat(wavestring,".wav ",6);	

	return 0;
}

void saynumber(int num)
{
	char string[10];

	sprintf(string,"%4d",num);

	if (string[1]!=32)
	{
		stringBuilder(numstr[string[1]-'0']);
		stringBuilder("hundred");
	}

	switch (string[2])
	{
		case '1':
			stringBuilder(numstr[(10+(string[3]-'0'))]);
			break;

		case '2':	
			stringBuilder("twenty");
			break;

		case '3':
			stringBuilder("thirty");
			break;

		case '4':
			stringBuilder("forty");
			break;

		case '5':
			stringBuilder("fifty");
			break;

		case '6':
			stringBuilder("sixty");
			break;

		case '7':
			stringBuilder("seventy");
			break;

		case '8':
			stringBuilder("eighty");
			break;

		case '9':
			stringBuilder("ninety");
			break;
	}

	if (string[3]!=32 && string[3]!=0 && string[2]!='1' && string[3]!='0')
		stringBuilder(numstr[string[3]-'0']);

	if (string[3]=='0' && string[2]==32 && string[1]==32)
		stringBuilder("zero"); 
}

void *vocalizer(void *arg)
{
	char *string=(char *)arg;
	char str1[20], str2[20], str3[20], str4[20], str5[20];
	int argnum=0, number;

	argnum=sscanf(string,"%s%s%s%s%s",str1,str2,str3,str4,str5);

	/* Specify the audio player to use */

	sprintf(wavestring,"play-audio -s media ");

	talking=1;

        if (argnum==1)
	{
		if (strncmp(str1,"eclipse",7)==0)
		{
			stringBuilder("alarm");
			stringBuilder("eclipse");
		}

		if (strncmp(str1,"sunlight",8)==0)
		{
			stringBuilder("alarm");
			stringBuilder("sunlight");
		}

		if (strncmp(str1,"los",3)==0)
			stringBuilder("los");
	}

	if (argnum>=2)
	{
		stringBuilder("intro");

		number=atoi(str1);
		saynumber(number);
		stringBuilder("azimuth");

		number=atoi(str2);
		saynumber(number);
		stringBuilder("elevation");

		if (argnum>=3)
		{
			if (str3[0]=='+')
				stringBuilder("approaching");

			if (str3[0]=='-')
				stringBuilder("receding");
		}

		if (argnum==4)
		{
			if (str4[0]=='V')
				stringBuilder("visible");
		}
	}

	/* Start talking! */

	system(wavestring);

	talking=0;

	return NULL;
}

#else

void *vocalizer(void *arg)
{
	/* If there is no ALSA or Android sound support... */

	return NULL;
}

#endif
#endif

/* Functions for testing and setting/clearing flags used in SGP4/SDP4 code */

int isFlagSet(int flag)
{
	return (Flags&flag);
}

int isFlagClear(int flag)
{
	return (~Flags&flag);
}

void SetFlag(int flag)
{
	Flags|=flag;
}

void ClearFlag(int flag)
{
	Flags&=~flag;
}

/* Remaining SGP4/SDP4 code follows... */

int Sign(double arg)
{
	/* Returns sign of a double */

	if (arg>0)
		return 1;
		
	else if (arg<0)
		return -1;
		
	else
		return 0;
}

double Sqr(double arg)
{
	/* Returns square of a double */
	return (arg*arg);
}

double Radians(double arg)
{
	/* Returns angle in radians from argument in degrees */
	return (arg*deg2rad);
}

double Degrees(double arg)
{
	/* Returns angle in degrees from argument in radians */
	return (arg/deg2rad);
}

double ArcSin(double arg)
{
	/* Returns the arcsine of the argument */

	if (fabs(arg)>=1.0)
		return(Sign(arg)*pio2);
	else

	return(atan(arg/sqrt(1.0-arg*arg)));
}

double ArcCos(double arg)
{
	/* Returns arccosine of argument */
	return(pio2-ArcSin(arg));
}

void Magnitude(vector_t *v)
{
	/* Calculates scalar magnitude of a vector_t argument */
	v->w=sqrt(Sqr(v->x)+Sqr(v->y)+Sqr(v->z));
}

void Vec_Sub(vector_t *v1, vector_t *v2, vector_t *v3)
{
	/* Subtracts vector v2 from v1 to produce v3 */
	v3->x=v1->x-v2->x;
	v3->y=v1->y-v2->y;
	v3->z=v1->z-v2->z;
	Magnitude(v3);
}

void Scalar_Multiply(double k, vector_t *v1, vector_t *v2)
{
	/* Multiplies the vector v1 by the scalar k to produce the vector v2 */
	v2->x=k*v1->x;
	v2->y=k*v1->y;
	v2->z=k*v1->z;
	v2->w=fabs(k)*v1->w;
}

void Scale_Vector(double k, vector_t *v)
{ 
	/* Multiplies the vector v1 by the scalar k */
	v->x*=k;
	v->y*=k;
	v->z*=k;
	Magnitude(v);
}

double Dot(vector_t *v1, vector_t *v2)
{
	/* Returns the dot product of two vectors */
	return (v1->x*v2->x+v1->y*v2->y+v1->z*v2->z);
}

double Angle(vector_t *v1, vector_t *v2)
{
	/* Calculates the angle between vectors v1 and v2 */
	Magnitude(v1);
	Magnitude(v2);
	return(ArcCos(Dot(v1,v2)/(v1->w*v2->w)));
}

void Cross(vector_t *v1, vector_t *v2 ,vector_t *v3)
{
	/* Produces cross product of v1 and v2, and returns in v3 */
	v3->x=v1->y*v2->z-v1->z*v2->y;
	v3->y=v1->z*v2->x-v1->x*v2->z;
	v3->z=v1->x*v2->y-v1->y*v2->x;
	Magnitude(v3);
}

double AcTan(double sinx, double cosx)
{
	/* Four-quadrant arctan function */

	if (cosx==0.0)
	{
		if (sinx>0.0)
			return (pio2);
		else
			return (x3pio2);
	}

	else
	{
		if (cosx>0.0)
		{
			if (sinx>0.0)
				return (atan(sinx/cosx));
			else
				return (twopi+atan(sinx/cosx));
		}

		else
			return (pi+atan(sinx/cosx));
	}
}

double FMod2p(double x)
{
	/* Returns mod 2PI of argument */

	int i;
	double ret_val;

	ret_val=x;
	i=ret_val/twopi;
	ret_val-=i*twopi;

	if (ret_val<0.0)
		ret_val+=twopi;

	return ret_val;
}

double Modulus(double arg1, double arg2)
{
	/* Returns arg1 mod arg2 */

	int i;
	double ret_val;

	ret_val=arg1;
	i=ret_val/arg2;
	ret_val-=i*arg2;

	if (ret_val<0.0)
		ret_val+=arg2;

	return ret_val;
}

double Frac(double arg)
{
	/* Returns fractional part of double argument */
	return(arg-floor(arg));
}

void Convert_Sat_State(vector_t *pos, vector_t *vel)
{
	/* Converts the satellite's position and velocity  */
	/* vectors from normalized values to km and km/sec */ 
	Scale_Vector(xkmper, pos);
	Scale_Vector(xkmper*xmnpda/secday, vel);
}

double Julian_Date_of_Year(double year)
{
	/* The function Julian_Date_of_Year calculates */
	/* the Julian Date of Day 0.0 of {year}. */

	/* Astronomical Formulae for Calculators, Jean Meeus, */
	/* pages 23-25. Calculate Julian Date of 0.0 Jan year */

	long A, B, i;
	double jdoy;

	year=year-1;
	i=year/100;
	A=i;
	i=A/4;
	B=2-A+i;
	i=365.25*year;
	i+=30.6001*14;
	jdoy=i+1720994.5+B;

	return jdoy;
}

double Julian_Date_of_Epoch(double day, double year)
{
	/* The function Julian_Date_of_Epoch returns the Julian Date */
	/* of an epoch specified in the Keplerian Orbital Data Set.  */

	return (Julian_Date_of_Year(year)+day);
}

double Fraction_of_Day(int hr, int mi, double se)
{
	/* Fraction_of_Day calculates the fraction of */
	/* a day passed at the specified input time.  */

	double dhr, dmi;

	dhr=(double)hr;
	dmi=(double)mi;

	return ((dhr+(dmi+se/60.0)/60.0)/24.0);
}

double Delta_ET(double year)
{
	/* The function Delta_ET has been added to allow calculations on   */
	/* the position of the sun.  It provides the difference between UT */
	/* (approximately the same as UTC) and ET (now referred to as TDT).*/
	/* This function is based on a least squares fit of data from 1950 */
	/* to 1991 and will need to be updated periodically. */

	/* Values determined using data from 1950-1991 in the 1990 
	Astronomical Almanac.  See DELTA_ET.WQ1 for details. */

	double delta_et;

	delta_et=26.465+0.747622*(year-1950)+1.886913*sin(twopi*(year-1975)/33);

	return delta_et;
}

double ThetaG(double day, double year, deep_arg_t *deep_arg)
{
	/* The function ThetaG calculates the Greenwich Mean Sidereal Time */
	/* for an epoch specified in the Keplerian Orbital Data Set. The   */
	/* function ThetaG_JD provides the same calculation except that it */
	/* is based on an input in the form of a Julian Date. */

	/* Reference:  The 1992 Astronomical Almanac, page B6. */

	double UT, jd, TU, GMST, ThetaG;

	UT=modf(day,&day);
	jd=Julian_Date_of_Year(year)+day;
	TU=(jd-2451545.0)/36525;
	GMST=24110.54841+TU*(8640184.812866+TU*(0.093104-TU*6.2E-6));
	GMST=Modulus(GMST+secday*omega_E*UT,secday);
	ThetaG=twopi*GMST/secday;
	deep_arg->ds50=jd-2433281.5+UT;
	ThetaG=FMod2p(6.3003880987*deep_arg->ds50+1.72944494);

	return ThetaG;
}

double ThetaG_JD(double jd)
{
	/* Reference:  The 1992 Astronomical Almanac, page B6. */

	double UT, TU, GMST;

	UT=Frac(jd+0.5);
	jd=jd-UT;
	TU=(jd-2451545.0)/36525;
	GMST=24110.54841+TU*(8640184.812866+TU*(0.093104-TU*6.2E-6));
	GMST=Modulus(GMST+secday*omega_E*UT,secday);

	return (twopi*GMST/secday);
}

void Calculate_Solar_Position(double time, vector_t *solar_vector)
{
	/* Calculates solar position vector */

	double mjd, year, T, M, L, e, C, O, Lsa, nu, R, eps;

	mjd=time-2415020.0;
	year=1900+mjd/365.25;
	T=(mjd+Delta_ET(year)/secday)/36525.0;
	M=Radians(Modulus(358.47583+Modulus(35999.04975*T,360.0)-(0.000150+0.0000033*T)*Sqr(T),360.0));
	L=Radians(Modulus(279.69668+Modulus(36000.76892*T,360.0)+0.0003025*Sqr(T),360.0));
	e=0.01675104-(0.0000418+0.000000126*T)*T;
	C=Radians((1.919460-(0.004789+0.000014*T)*T)*sin(M)+(0.020094-0.000100*T)*sin(2*M)+0.000293*sin(3*M));
	O=Radians(Modulus(259.18-1934.142*T,360.0));
	Lsa=Modulus(L+C-Radians(0.00569-0.00479*sin(O)),twopi);
	nu=Modulus(M+C,twopi);
	R=1.0000002*(1.0-Sqr(e))/(1.0+e*cos(nu));
	eps=Radians(23.452294-(0.0130125+(0.00000164-0.000000503*T)*T)*T+0.00256*cos(O));
	R=AU*R;
	solar_vector->x=R*cos(Lsa);
	solar_vector->y=R*sin(Lsa)*cos(eps);
	solar_vector->z=R*sin(Lsa)*sin(eps);
	solar_vector->w=R;
}

int Sat_Eclipsed(vector_t *pos, vector_t *sol, double *depth)
{
	/* Calculates satellite's eclipse status and depth */

	double sd_sun, sd_earth, delta;
	vector_t Rho, earth;

	/* Determine partial eclipse */

	sd_earth=ArcSin(xkmper/pos->w);
	Vec_Sub(sol,pos,&Rho);
	sd_sun=ArcSin(sr/Rho.w);
	Scalar_Multiply(-1,pos,&earth);
	delta=Angle(sol,&earth);
	*depth=sd_earth-sd_sun-delta;

	if (sd_earth<sd_sun)
		return 0;
	else
		if (*depth>=0)
			return 1;
	else
		return 0;
}

void select_ephemeris(tle_t *tle)
{
	/* Selects the apropriate ephemeris type to be used.  */
	/* It also processes values in the Keplerian data set */
	/* that are apropriate for the sgp4/sdp4 routines.    */

	double ao, xnodp, dd1, dd2, delo, temp, a1, del1, r1;

	/* Preprocess tle set */
	tle->xnodeo*=deg2rad;
	tle->omegao*=deg2rad;
	tle->xmo*=deg2rad;
	tle->xincl*=deg2rad;
	temp=twopi/xmnpda/xmnpda;
	tle->xno=tle->xno*temp*xmnpda;
	tle->xndt2o*=temp;
	tle->xndd6o=tle->xndd6o*temp/xmnpda;
	tle->bstar/=ae;

	/* Period > 225 minutes is deep space */
	dd1=(xke/tle->xno);
	dd2=tothrd;
	a1=pow(dd1,dd2);
	r1=cos(tle->xincl);
	dd1=(1.0-tle->eo*tle->eo);
	temp=ck2*1.5f*(r1*r1*3.0-1.0)/pow(dd1,1.5);
	del1=temp/(a1*a1);
	ao=a1*(1.0-del1*(tothrd*.5+del1*(del1*1.654320987654321+1.0)));
	delo=temp/(ao*ao);
	xnodp=tle->xno/(delo+1.0);

	/* Select a deep-space/near-earth ephemeris */

	if (twopi/xnodp/xmnpda>=0.15625)
		SetFlag(DEEP_SPACE_EPHEM_FLAG);
	else
		ClearFlag(DEEP_SPACE_EPHEM_FLAG);
}

void SGP4(double tsince, tle_t * tle, vector_t * pos, vector_t * vel)
{
	/* This function is used to calculate the position and velocity */
	/* of near-earth (period < 225 minutes) satellites. tsince is   */
	/* time since epoch in minutes, tle is a pointer to a tle_t     */
	/* structure with Keplerian orbital elements and pos and vel    */
	/* are vector_t structures returning ECI satellite position and */ 
	/* velocity. Use Convert_Sat_State() to convert to km and km/s. */

	static double aodp, aycof, c1, c4, c5, cosio, d2, d3, d4, delmo,
	omgcof, eta, omgdot, sinio, xnodp, sinmo, t2cof, t3cof, t4cof,
	t5cof, x1mth2, x3thm1, x7thm1, xmcof, xmdot, xnodcf, xnodot, xlcof;

	double cosuk, sinuk, rfdotk, vx, vy, vz, ux, uy, uz, xmy, xmx, cosnok,
	sinnok, cosik, sinik, rdotk, xinck, xnodek, uk, rk, cos2u, sin2u,
	u, sinu, cosu, betal, rfdot, rdot, r, pl, elsq, esine, ecose, epw,
	cosepw, x1m5th, xhdot1, tfour, sinepw, capu, ayn, xlt, aynl, xll,
	axn, xn, beta, xl, e, a, tcube, delm, delomg, templ, tempe, tempa,
	xnode, tsq, xmp, omega, xnoddf, omgadf, xmdf, a1, a3ovk2, ao,
	betao, betao2, c1sq, c2, c3, coef, coef1, del1, delo, eeta, eosq,
	etasq, perigee, pinvsq, psisq, qoms24, s4, temp, temp1, temp2,
	temp3, temp4, temp5, temp6, theta2, theta4, tsi;

	int i;

	/* Initialization */

	if (isFlagClear(SGP4_INITIALIZED_FLAG))
	{
		SetFlag(SGP4_INITIALIZED_FLAG);

		/* Recover original mean motion (xnodp) and   */
		/* semimajor axis (aodp) from input elements. */

		a1=pow(xke/tle->xno,tothrd);
		cosio=cos(tle->xincl);
		theta2=cosio*cosio;
		x3thm1=3*theta2-1.0;
		eosq=tle->eo*tle->eo;
		betao2=1.0-eosq;
		betao=sqrt(betao2);
		del1=1.5*ck2*x3thm1/(a1*a1*betao*betao2);
		ao=a1*(1.0-del1*(0.5*tothrd+del1*(1.0+134.0/81.0*del1)));
		delo=1.5*ck2*x3thm1/(ao*ao*betao*betao2);
		xnodp=tle->xno/(1.0+delo);
		aodp=ao/(1.0-delo);

		/* For perigee less than 220 kilometers, the "simple"     */
		/* flag is set and the equations are truncated to linear  */
		/* variation in sqrt a and quadratic variation in mean    */
		/* anomaly.  Also, the c3 term, the delta omega term, and */
		/* the delta m term are dropped.                          */

		if ((aodp*(1-tle->eo)/ae)<(220/xkmper+ae))
		    SetFlag(SIMPLE_FLAG);

		else
		    ClearFlag(SIMPLE_FLAG);

		/* For perigees below 156 km, the      */
		/* values of s and qoms2t are altered. */

		s4=s;
		qoms24=qoms2t;
		perigee=(aodp*(1-tle->eo)-ae)*xkmper;

		if (perigee<156.0)
		{
			if (perigee<=98.0)
			    s4=20;
			else
		   	 s4=perigee-78.0;

			qoms24=pow((120-s4)*ae/xkmper,4);
			s4=s4/xkmper+ae;
		}

		pinvsq=1/(aodp*aodp*betao2*betao2);
		tsi=1/(aodp-s4);
		eta=aodp*tle->eo*tsi;
		etasq=eta*eta;
		eeta=tle->eo*eta;
		psisq=fabs(1-etasq);
		coef=qoms24*pow(tsi,4);
		coef1=coef/pow(psisq,3.5);
		c2=coef1*xnodp*(aodp*(1+1.5*etasq+eeta*(4+etasq))+0.75*ck2*tsi/psisq*x3thm1*(8+3*etasq*(8+etasq)));
		c1=tle->bstar*c2;
		sinio=sin(tle->xincl);
		a3ovk2=-xj3/ck2*pow(ae,3);
		c3=coef*tsi*a3ovk2*xnodp*ae*sinio/tle->eo;
		x1mth2=1-theta2;

		c4=2*xnodp*coef1*aodp*betao2*(eta*(2+0.5*etasq)+tle->eo*(0.5+2*etasq)-2*ck2*tsi/(aodp*psisq)*(-3*x3thm1*(1-2*eeta+etasq*(1.5-0.5*eeta))+0.75*x1mth2*(2*etasq-eeta*(1+etasq))*cos(2*tle->omegao)));
		c5=2*coef1*aodp*betao2*(1+2.75*(etasq+eeta)+eeta*etasq);

		theta4=theta2*theta2;
		temp1=3*ck2*pinvsq*xnodp;
		temp2=temp1*ck2*pinvsq;
		temp3=1.25*ck4*pinvsq*pinvsq*xnodp;
		xmdot=xnodp+0.5*temp1*betao*x3thm1+0.0625*temp2*betao*(13-78*theta2+137*theta4);
		x1m5th=1-5*theta2;
		omgdot=-0.5*temp1*x1m5th+0.0625*temp2*(7-114*theta2+395*theta4)+temp3*(3-36*theta2+49*theta4);
		xhdot1=-temp1*cosio;
		xnodot=xhdot1+(0.5*temp2*(4-19*theta2)+2*temp3*(3-7*theta2))*cosio;
		omgcof=tle->bstar*c3*cos(tle->omegao);
		xmcof=-tothrd*coef*tle->bstar*ae/eeta;
		xnodcf=3.5*betao2*xhdot1*c1;
		t2cof=1.5*c1;
		xlcof=0.125*a3ovk2*sinio*(3+5*cosio)/(1+cosio);
		aycof=0.25*a3ovk2*sinio;
		delmo=pow(1+eta*cos(tle->xmo),3);
		sinmo=sin(tle->xmo);
		x7thm1=7*theta2-1;

		if (isFlagClear(SIMPLE_FLAG))
		{
			c1sq=c1*c1;
			d2=4*aodp*tsi*c1sq;
			temp=d2*tsi*c1/3;
			d3=(17*aodp+s4)*temp;
			d4=0.5*temp*aodp*tsi*(221*aodp+31*s4)*c1;
			t3cof=d2+2*c1sq;
			t4cof=0.25*(3*d3+c1*(12*d2+10*c1sq));
			t5cof=0.2*(3*d4+12*c1*d3+6*d2*d2+15*c1sq*(2*d2+c1sq));
		}
	}

	/* Update for secular gravity and atmospheric drag. */
	xmdf=tle->xmo+xmdot*tsince;
	omgadf=tle->omegao+omgdot*tsince;
	xnoddf=tle->xnodeo+xnodot*tsince;
	omega=omgadf;
	xmp=xmdf;
	tsq=tsince*tsince;
	xnode=xnoddf+xnodcf*tsq;
	tempa=1-c1*tsince;
	tempe=tle->bstar*c4*tsince;
	templ=t2cof*tsq;
    
	if (isFlagClear(SIMPLE_FLAG))
	{
		delomg=omgcof*tsince;
		delm=xmcof*(pow(1+eta*cos(xmdf),3)-delmo);
		temp=delomg+delm;
		xmp=xmdf+temp;
		omega=omgadf-temp;
		tcube=tsq*tsince;
		tfour=tsince*tcube;
		tempa=tempa-d2*tsq-d3*tcube-d4*tfour;
		tempe=tempe+tle->bstar*c5*(sin(xmp)-sinmo);
		templ=templ+t3cof*tcube+tfour*(t4cof+tsince*t5cof);
	}

	a=aodp*pow(tempa,2);
	e=tle->eo-tempe;
	xl=xmp+omega+xnode+xnodp*templ;
	beta=sqrt(1-e*e);
	xn=xke/pow(a,1.5);

	/* Long period periodics */
	axn=e*cos(omega);
	temp=1/(a*beta*beta);
	xll=temp*xlcof*axn;
	aynl=temp*aycof;
	xlt=xl+xll;
	ayn=e*sin(omega)+aynl;

	/* Solve Kepler's Equation */
	capu=FMod2p(xlt-xnode);
	temp2=capu;
	i=0;

	do
	{
		sinepw=sin(temp2);
		cosepw=cos(temp2);
		temp3=axn*sinepw;
		temp4=ayn*cosepw;
		temp5=axn*cosepw;
		temp6=ayn*sinepw;
		epw=(capu-temp4+temp3-temp2)/(1-temp5-temp6)+temp2;
	  
		if (fabs(epw-temp2)<= e6a)
			break;
	      
		temp2=epw;

	} while (i++<10);

	/* Short period preliminary quantities */
	ecose=temp5+temp6;
	esine=temp3-temp4;
	elsq=axn*axn+ayn*ayn;
	temp=1-elsq;
	pl=a*temp;
	r=a*(1-ecose);
	temp1=1/r;
	rdot=xke*sqrt(a)*esine*temp1;
	rfdot=xke*sqrt(pl)*temp1;
	temp2=a*temp1;
	betal=sqrt(temp);
	temp3=1/(1+betal);
	cosu=temp2*(cosepw-axn+ayn*esine*temp3);
	sinu=temp2*(sinepw-ayn-axn*esine*temp3);
	u=AcTan(sinu,cosu);
	sin2u=2*sinu*cosu;
	cos2u=2*cosu*cosu-1;
	temp=1/pl;
	temp1=ck2*temp;
	temp2=temp1*temp;

	/* Update for short periodics */
	rk=r*(1-1.5*temp2*betal*x3thm1)+0.5*temp1*x1mth2*cos2u;
	uk=u-0.25*temp2*x7thm1*sin2u;
	xnodek=xnode+1.5*temp2*cosio*sin2u;
	xinck=tle->xincl+1.5*temp2*cosio*sinio*cos2u;
	rdotk=rdot-xn*temp1*x1mth2*sin2u;
	rfdotk=rfdot+xn*temp1*(x1mth2*cos2u+1.5*x3thm1);

	/* Orientation vectors */
	sinuk=sin(uk);
	cosuk=cos(uk);
	sinik=sin(xinck);
	cosik=cos(xinck);
	sinnok=sin(xnodek);
	cosnok=cos(xnodek);
	xmx=-sinnok*cosik;
	xmy=cosnok*cosik;
	ux=xmx*sinuk+cosnok*cosuk;
	uy=xmy*sinuk+sinnok*cosuk;
	uz=sinik*sinuk;
	vx=xmx*cosuk-cosnok*sinuk;
	vy=xmy*cosuk-sinnok*sinuk;
	vz=sinik*cosuk;

	/* Position and velocity */
	pos->x=rk*ux;
	pos->y=rk*uy;
	pos->z=rk*uz;
	vel->x=rdotk*ux+rfdotk*vx;
	vel->y=rdotk*uy+rfdotk*vy;
	vel->z=rdotk*uz+rfdotk*vz;

	/* Phase in radians */
	phase=xlt-xnode-omgadf+twopi;
    
	if (phase<0.0)
		phase+=twopi;

	phase=FMod2p(phase);
}

void Deep(int ientry, tle_t * tle, deep_arg_t * deep_arg)
{
	/* This function is used by SDP4 to add lunar and solar */
	/* perturbation effects to deep-space orbit objects.    */

	static double thgr, xnq, xqncl, omegaq, zmol, zmos, savtsn, ee2, e3,
	xi2, xl2, xl3, xl4, xgh2, xgh3, xgh4, xh2, xh3, sse, ssi, ssg, xi3,
	se2, si2, sl2, sgh2, sh2, se3, si3, sl3, sgh3, sh3, sl4, sgh4, ssl,
	ssh, d3210, d3222, d4410, d4422, d5220, d5232, d5421, d5433, del1,
	del2, del3, fasx2, fasx4, fasx6, xlamo, xfact, xni, atime, stepp,
	stepn, step2, preep, pl, sghs, xli, d2201, d2211, sghl, sh1, pinc,
	pe, shs, zsingl, zcosgl, zsinhl, zcoshl, zsinil, zcosil;

	double a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ainv2, alfdp, aqnv,
	sgh, sini2, sinis, sinok, sh, si, sil, day, betdp, dalf, bfact, c,
	cc, cosis, cosok, cosq, ctem, f322, zx, zy, dbet, dls, eoc, eq, f2,
	f220, f221, f3, f311, f321, xnoh, f330, f441, f442, f522, f523,
	f542, f543, g200, g201, g211, pgh, ph, s1, s2, s3, s4, s5, s6, s7,
	se, sel, ses, xls, g300, g310, g322, g410, g422, g520, g521, g532,
	g533, gam, sinq, sinzf, sis, sl, sll, sls, stem, temp, temp1, x1,
	x2, x2li, x2omi, x3, x4, x5, x6, x7, x8, xl, xldot, xmao, xnddt,
	xndot, xno2, xnodce, xnoi, xomi, xpidot, z1, z11, z12, z13, z2,
	z21, z22, z23, z3, z31, z32, z33, ze, zf, zm, zn, zsing, zsinh,
	zsini, zcosg, zcosh, zcosi, delt=0, ft=0;

	switch (ientry)
	{
		case dpinit:  /* Entrance for deep space initialization */
		thgr=ThetaG(tle->day, tle->year, deep_arg);
		eq=tle->eo;
		xnq=deep_arg->xnodp;
		aqnv=1/deep_arg->aodp;
		xqncl=tle->xincl;
		xmao=tle->xmo;
		xpidot=deep_arg->omgdot+deep_arg->xnodot;
		sinq=sin(tle->xnodeo);
		cosq=cos(tle->xnodeo);
		omegaq=tle->omegao;

		/* Initialize lunar solar terms */
		day=deep_arg->ds50+18261.5;  /* Days since 1900 Jan 0.5 */
	  
		if (day!=preep)
		{
			preep=day;
			xnodce=4.5236020-9.2422029E-4*day;
			stem=sin(xnodce);
			ctem=cos(xnodce);
			zcosil=0.91375164-0.03568096*ctem;
			zsinil=sqrt(1-zcosil*zcosil);
			zsinhl=0.089683511*stem/zsinil;
			zcoshl=sqrt(1-zsinhl*zsinhl);
			c=4.7199672+0.22997150*day;
			gam=5.8351514+0.0019443680*day;
			zmol=FMod2p(c-gam);
			zx=0.39785416*stem/zsinil;
			zy=zcoshl*ctem+0.91744867*zsinhl*stem;
			zx=AcTan(zx,zy);
			zx=gam+zx-xnodce;
			zcosgl=cos(zx);
			zsingl=sin(zx);
			zmos=6.2565837+0.017201977*day;
			zmos=FMod2p(zmos);
		    }

		  /* Do solar terms */
		  savtsn=1E20;
		  zcosg=zcosgs;
		  zsing=zsings;
		  zcosi=zcosis;
		  zsini=zsinis;
		  zcosh=cosq;
		  zsinh= sinq;
		  cc=c1ss;
		  zn=zns;
		  ze=zes;
		  xnoi=1/xnq;

		  /* Loop breaks when Solar terms are done a second */
		  /* time, after Lunar terms are initialized        */
	  
		for (;;)
		{
			/* Solar terms done again after Lunar terms are done */
			a1=zcosg*zcosh+zsing*zcosi*zsinh;
			a3=-zsing*zcosh+zcosg*zcosi*zsinh;
			a7=-zcosg*zsinh+zsing*zcosi*zcosh;
			a8=zsing*zsini;
			a9=zsing*zsinh+zcosg*zcosi*zcosh;
			a10=zcosg*zsini;
			a2=deep_arg->cosio*a7+deep_arg->sinio*a8;
			a4=deep_arg->cosio*a9+deep_arg->sinio*a10;
			a5=-deep_arg->sinio*a7+deep_arg->cosio*a8;
			a6=-deep_arg->sinio*a9+deep_arg->cosio*a10;
			x1=a1*deep_arg->cosg+a2*deep_arg->sing;
			x2=a3*deep_arg->cosg+a4*deep_arg->sing;
			x3=-a1*deep_arg->sing+a2*deep_arg->cosg;
			x4=-a3*deep_arg->sing+a4*deep_arg->cosg;
			x5=a5*deep_arg->sing;
			x6=a6*deep_arg->sing;
			x7=a5*deep_arg->cosg;
			x8=a6*deep_arg->cosg;
			z31=12*x1*x1-3*x3*x3;
			z32=24*x1*x2-6*x3*x4;
			z33=12*x2*x2-3*x4*x4;
			z1=3*(a1*a1+a2*a2)+z31*deep_arg->eosq;
			z2=6*(a1*a3+a2*a4)+z32*deep_arg->eosq;
			z3=3*(a3*a3+a4*a4)+z33*deep_arg->eosq;
			z11=-6*a1*a5+deep_arg->eosq*(-24*x1*x7-6*x3*x5);
			z12=-6*(a1*a6+a3*a5)+deep_arg->eosq*(-24*(x2*x7+x1*x8)-6*(x3*x6+x4*x5));
			z13=-6*a3*a6+deep_arg->eosq*(-24*x2*x8-6*x4*x6);
			z21=6*a2*a5+deep_arg->eosq*(24*x1*x5-6*x3*x7);
			z22=6*(a4*a5+a2*a6)+deep_arg->eosq*(24*(x2*x5+x1*x6)-6*(x4*x7+x3*x8));
			z23=6*a4*a6+deep_arg->eosq*(24*x2*x6-6*x4*x8);
			z1=z1+z1+deep_arg->betao2*z31;
			z2=z2+z2+deep_arg->betao2*z32;
			z3=z3+z3+deep_arg->betao2*z33;
			s3=cc*xnoi;
			s2=-0.5*s3/deep_arg->betao;
			s4=s3*deep_arg->betao;
			s1=-15*eq*s4;
			s5=x1*x3+x2*x4;
			s6=x2*x3+x1*x4;
			s7=x2*x4-x1*x3;
			se=s1*zn*s5;
			si=s2*zn*(z11+z13);
			sl=-zn*s3*(z1+z3-14-6*deep_arg->eosq);
			sgh=s4*zn*(z31+z33-6);
			sh=-zn*s2*(z21+z23);
		
			if (xqncl<5.2359877E-2)
				sh=0;
		    
			ee2=2*s1*s6;
			e3=2*s1*s7;
			xi2=2*s2*z12;
			xi3=2*s2*(z13-z11);
			xl2=-2*s3*z2;
			xl3=-2*s3*(z3-z1);
			xl4=-2*s3*(-21-9*deep_arg->eosq)*ze;
			xgh2=2*s4*z32;
			xgh3=2*s4*(z33-z31);
			xgh4=-18*s4*ze;
			xh2=-2*s2*z22;
			xh3=-2*s2*(z23-z21);

			if (isFlagSet(LUNAR_TERMS_DONE_FLAG))
				break;

			/* Do lunar terms */
			sse=se;
			ssi=si;
			ssl=sl;
			ssh=sh/deep_arg->sinio;
			ssg=sgh-deep_arg->cosio*ssh;
			se2=ee2;
			si2=xi2;
			sl2=xl2;
			sgh2=xgh2;
			sh2=xh2;
			se3=e3;
			si3=xi3;
			sl3=xl3;
			sgh3=xgh3;
			sh3=xh3;
			sl4=xl4;
			sgh4=xgh4;
			zcosg=zcosgl;
			zsing=zsingl;
			zcosi=zcosil;
			zsini=zsinil;
			zcosh=zcoshl*cosq+zsinhl*sinq;
			zsinh=sinq*zcoshl-cosq*zsinhl;
			zn=znl;
			cc=c1l;
			ze=zel;
			SetFlag(LUNAR_TERMS_DONE_FLAG);
		}

		sse=sse+se;
		ssi=ssi+si;
		ssl=ssl+sl;
		ssg=ssg+sgh-deep_arg->cosio/deep_arg->sinio*sh;
		ssh=ssh+sh/deep_arg->sinio;

		/* Geopotential resonance initialization for 12 hour orbits */
		ClearFlag(RESONANCE_FLAG);
		ClearFlag(SYNCHRONOUS_FLAG);

		if (!((xnq<0.0052359877) && (xnq>0.0034906585)))
		{
			if ((xnq<0.00826) || (xnq>0.00924))
			    return;
	
			if (eq<0.5)
			    return;
	
			SetFlag(RESONANCE_FLAG);
			eoc=eq*deep_arg->eosq;
			g201=-0.306-(eq-0.64)*0.440;
		
			if (eq<=0.65)
			{
				g211=3.616-13.247*eq+16.290*deep_arg->eosq;
				g310=-19.302+117.390*eq-228.419*deep_arg->eosq+156.591*eoc;
				g322=-18.9068+109.7927*eq-214.6334*deep_arg->eosq+146.5816*eoc;
				g410=-41.122+242.694*eq-471.094*deep_arg->eosq+313.953*eoc;
				g422=-146.407+841.880*eq-1629.014*deep_arg->eosq+1083.435 * eoc;
				g520=-532.114+3017.977*eq-5740*deep_arg->eosq+3708.276*eoc;
			}
		
			else
			{
				g211=-72.099+331.819*eq-508.738*deep_arg->eosq+266.724*eoc;
				g310=-346.844+1582.851*eq-2415.925*deep_arg->eosq+1246.113*eoc;
				g322=-342.585+1554.908*eq-2366.899*deep_arg->eosq+1215.972*eoc;
				g410=-1052.797+4758.686*eq-7193.992*deep_arg->eosq+3651.957*eoc;
				g422=-3581.69+16178.11*eq-24462.77*deep_arg->eosq+12422.52*eoc;
		      
				if (eq<=0.715)
					g520=1464.74-4664.75*eq+3763.64*deep_arg->eosq;
			  
				else
					g520=-5149.66+29936.92*eq-54087.36*deep_arg->eosq+31324.56*eoc;
			}

			if (eq<0.7)
			{
				g533=-919.2277+4988.61*eq-9064.77*deep_arg->eosq+5542.21*eoc;
				g521=-822.71072+4568.6173*eq-8491.4146*deep_arg->eosq+5337.524*eoc;
				g532=-853.666+4690.25*eq-8624.77*deep_arg->eosq+5341.4*eoc;
			}
		
			else
			{
				g533=-37995.78+161616.52*eq-229838.2*deep_arg->eosq+109377.94*eoc;
				g521 =-51752.104+218913.95*eq-309468.16*deep_arg->eosq+146349.42*eoc;
				g532 =-40023.88+170470.89*eq-242699.48*deep_arg->eosq+115605.82*eoc;
			}

			sini2=deep_arg->sinio*deep_arg->sinio;
			f220=0.75*(1+2*deep_arg->cosio+deep_arg->theta2);
			f221=1.5*sini2;
			f321=1.875*deep_arg->sinio*(1-2*deep_arg->cosio-3*deep_arg->theta2);
			f322=-1.875*deep_arg->sinio*(1+2*deep_arg->cosio-3*deep_arg->theta2);
			f441=35*sini2*f220;
			f442=39.3750*sini2*sini2;
			f522=9.84375*deep_arg->sinio*(sini2*(1-2*deep_arg->cosio-5*deep_arg->theta2)+0.33333333*(-2+4*deep_arg->cosio+6*deep_arg->theta2));
			f523=deep_arg->sinio*(4.92187512*sini2*(-2-4*deep_arg->cosio+10*deep_arg->theta2)+6.56250012*(1+2*deep_arg->cosio-3*deep_arg->theta2));
			f542=29.53125*deep_arg->sinio*(2-8*deep_arg->cosio+deep_arg->theta2*(-12+8*deep_arg->cosio+10*deep_arg->theta2));
			f543=29.53125*deep_arg->sinio*(-2-8*deep_arg->cosio+deep_arg->theta2*(12+8*deep_arg->cosio-10*deep_arg->theta2));
			xno2=xnq*xnq;
			ainv2=aqnv*aqnv;
			temp1=3*xno2*ainv2;
			temp=temp1*root22;
			d2201=temp*f220*g201;
			d2211=temp*f221*g211;
			temp1=temp1*aqnv;
			temp=temp1*root32;
			d3210=temp*f321*g310;
			d3222=temp*f322*g322;
			temp1=temp1*aqnv;
			temp=2*temp1*root44;
			d4410=temp*f441*g410;
			d4422=temp*f442*g422;
			temp1=temp1*aqnv;
			temp=temp1*root52;
			d5220=temp*f522*g520;
			d5232=temp*f523*g532;
			temp=2*temp1*root54;
			d5421=temp*f542*g521;
			d5433=temp*f543*g533;
			xlamo=xmao+tle->xnodeo+tle->xnodeo-thgr-thgr;
			bfact=deep_arg->xmdot+deep_arg->xnodot+deep_arg->xnodot-thdt-thdt;
			bfact=bfact+ssl+ssh+ssh;
		}
	
		else
		{
			SetFlag(RESONANCE_FLAG);
			SetFlag(SYNCHRONOUS_FLAG);
	
			/* Synchronous resonance terms initialization */
			g200=1+deep_arg->eosq*(-2.5+0.8125*deep_arg->eosq);
			g310=1+2*deep_arg->eosq;
			g300=1+deep_arg->eosq*(-6+6.60937*deep_arg->eosq);
			f220=0.75*(1+deep_arg->cosio)*(1+deep_arg->cosio);
			f311=0.9375*deep_arg->sinio*deep_arg->sinio*(1+3*deep_arg->cosio)-0.75*(1+deep_arg->cosio);
			f330=1+deep_arg->cosio;
			f330=1.875*f330*f330*f330;
			del1=3*xnq*xnq*aqnv*aqnv;
			del2=2*del1*f220*g200*q22;
			del3=3*del1*f330*g300*q33*aqnv;
			del1=del1*f311*g310*q31*aqnv;
			fasx2=0.13130908;
			fasx4=2.8843198;
			fasx6=0.37448087;
			xlamo=xmao+tle->xnodeo+tle->omegao-thgr;
			bfact=deep_arg->xmdot+xpidot-thdt;
			bfact=bfact+ssl+ssg+ssh;
		}

		xfact=bfact-xnq;

		/* Initialize integrator */
		xli=xlamo;
		xni=xnq;
		atime=0;
		stepp=720;
		stepn=-720;
		step2=259200;

		return;

		case dpsec:  /* Entrance for deep space secular effects */
		deep_arg->xll=deep_arg->xll+ssl*deep_arg->t;
		deep_arg->omgadf=deep_arg->omgadf+ssg*deep_arg->t;
		deep_arg->xnode=deep_arg->xnode+ssh*deep_arg->t;
		deep_arg->em=tle->eo+sse*deep_arg->t;
		deep_arg->xinc=tle->xincl+ssi*deep_arg->t;
	  
		if (deep_arg->xinc<0)
		{
			deep_arg->xinc=-deep_arg->xinc;
			deep_arg->xnode=deep_arg->xnode+pi;
			deep_arg->omgadf=deep_arg->omgadf-pi;
		}
	
		if (isFlagClear(RESONANCE_FLAG))
		      return;

		do
		{
			if ((atime==0) || ((deep_arg->t>=0) && (atime<0)) || ((deep_arg->t<0) && (atime>=0)))
			{
				/* Epoch restart */

				if (deep_arg->t>=0)
					delt=stepp;
				else
					delt=stepn;

				atime=0;
				xni=xnq;
				xli=xlamo;
			}

			else
			{
				if (fabs(deep_arg->t)>=fabs(atime))
				{
					if (deep_arg->t>0)
						delt=stepp;
					else
						delt=stepn;
				}
			}
	    
			do
			{
				if (fabs(deep_arg->t-atime)>=stepp)
				{
					SetFlag(DO_LOOP_FLAG);
					ClearFlag(EPOCH_RESTART_FLAG);
				}
		
				else
				{
					ft=deep_arg->t-atime;
					ClearFlag(DO_LOOP_FLAG);
				}

				if (fabs(deep_arg->t)<fabs(atime))
				{
					if (deep_arg->t>=0)
						delt=stepn;
					else
						delt=stepp;

					SetFlag(DO_LOOP_FLAG | EPOCH_RESTART_FLAG);
				}

				/* Dot terms calculated */
				if (isFlagSet(SYNCHRONOUS_FLAG))
				{
					xndot=del1*sin(xli-fasx2)+del2*sin(2*(xli-fasx4))+del3*sin(3*(xli-fasx6));
					xnddt=del1*cos(xli-fasx2)+2*del2*cos(2*(xli-fasx4))+3*del3*cos(3*(xli-fasx6));
				}
		
				else
				{
					xomi=omegaq+deep_arg->omgdot*atime;
					x2omi=xomi+xomi;
					x2li=xli+xli;
					xndot=d2201*sin(x2omi+xli-g22)+d2211*sin(xli-g22)+d3210*sin(xomi+xli-g32)+d3222*sin(-xomi+xli-g32)+d4410*sin(x2omi+x2li-g44)+d4422*sin(x2li-g44)+d5220*sin(xomi+xli-g52)+d5232*sin(-xomi+xli-g52)+d5421*sin(xomi+x2li-g54)+d5433*sin(-xomi+x2li-g54);
					xnddt=d2201*cos(x2omi+xli-g22)+d2211*cos(xli-g22)+d3210*cos(xomi+xli-g32)+d3222*cos(-xomi+xli-g32)+d5220*cos(xomi+xli-g52)+d5232*cos(-xomi+xli-g52)+2*(d4410*cos(x2omi+x2li-g44)+d4422*cos(x2li-g44)+d5421*cos(xomi+x2li-g54)+d5433*cos(-xomi+x2li-g54));
				}

				xldot=xni+xfact;
				xnddt=xnddt*xldot;

				if (isFlagSet(DO_LOOP_FLAG))
				{
					xli=xli+xldot*delt+xndot*step2;
					xni=xni+xndot*delt+xnddt*step2;
					atime=atime+delt;
				}

			} while (isFlagSet(DO_LOOP_FLAG) && isFlagClear(EPOCH_RESTART_FLAG));

		} while (isFlagSet(DO_LOOP_FLAG) && isFlagSet(EPOCH_RESTART_FLAG));

		deep_arg->xn=xni+xndot*ft+xnddt*ft*ft*0.5;
		xl=xli+xldot*ft+xndot*ft*ft*0.5;
		temp=-deep_arg->xnode+thgr+deep_arg->t*thdt;

		if (isFlagClear(SYNCHRONOUS_FLAG))
			deep_arg->xll=xl+temp+temp;
   		else
			deep_arg->xll=xl-deep_arg->omgadf+temp;

		return;

		case dpper:	 /* Entrance for lunar-solar periodics */
		sinis=sin(deep_arg->xinc);
		cosis=cos(deep_arg->xinc);

		if (fabs(savtsn-deep_arg->t)>=30)
		{
			savtsn=deep_arg->t;
			zm=zmos+zns*deep_arg->t;
			zf=zm+2*zes*sin(zm);
			sinzf=sin(zf);
			f2=0.5*sinzf*sinzf-0.25;
			f3=-0.5*sinzf*cos(zf);
			ses=se2*f2+se3*f3;
			sis=si2*f2+si3*f3;
			sls=sl2*f2+sl3*f3+sl4*sinzf;
			sghs=sgh2*f2+sgh3*f3+sgh4*sinzf;
			shs=sh2*f2+sh3*f3;
			zm=zmol+znl*deep_arg->t;
			zf=zm+2*zel*sin(zm);
			sinzf=sin(zf);
			f2=0.5*sinzf*sinzf-0.25;
			f3=-0.5*sinzf*cos(zf);
			sel=ee2*f2+e3*f3;
			sil=xi2*f2+xi3*f3;
			sll=xl2*f2+xl3*f3+xl4*sinzf;
			sghl=xgh2*f2+xgh3*f3+xgh4*sinzf;
			sh1=xh2*f2+xh3*f3;
			pe=ses+sel;
			pinc=sis+sil;
			pl=sls+sll;
		}

		pgh=sghs+sghl;
		ph=shs+sh1;
		deep_arg->xinc=deep_arg->xinc+pinc;
		deep_arg->em=deep_arg->em+pe;

		if (xqncl>=0.2)
		{
			/* Apply periodics directly */
			ph=ph/deep_arg->sinio;
			pgh=pgh-deep_arg->cosio*ph;
			deep_arg->omgadf=deep_arg->omgadf+pgh;
			deep_arg->xnode=deep_arg->xnode+ph;
			deep_arg->xll=deep_arg->xll+pl;
		}
	
		else
		{
			/* Apply periodics with Lyddane modification */
			sinok=sin(deep_arg->xnode);
			cosok=cos(deep_arg->xnode);
			alfdp=sinis*sinok;
			betdp=sinis*cosok;
			dalf=ph*cosok+pinc*cosis*sinok;
			dbet=-ph*sinok+pinc*cosis*cosok;
			alfdp=alfdp+dalf;
			betdp=betdp+dbet;
			deep_arg->xnode=FMod2p(deep_arg->xnode);
			xls=deep_arg->xll+deep_arg->omgadf+cosis*deep_arg->xnode;
			dls=pl+pgh-pinc*deep_arg->xnode*sinis;
			xls=xls+dls;
			xnoh=deep_arg->xnode;
			deep_arg->xnode=AcTan(alfdp,betdp);

			/* This is a patch to Lyddane modification */
			/* suggested by Rob Matson. */
		
			if (fabs(xnoh-deep_arg->xnode)>pi)
			{
			      if (deep_arg->xnode<xnoh)
				  deep_arg->xnode+=twopi;
			      else
				  deep_arg->xnode-=twopi;
			}

			deep_arg->xll=deep_arg->xll+pl;
			deep_arg->omgadf=xls-deep_arg->xll-cos(deep_arg->xinc)*deep_arg->xnode;
		}
		return;
	}
}

void SDP4(double tsince, tle_t * tle, vector_t * pos, vector_t * vel)
{
	/* This function is used to calculate the position and velocity */
	/* of deep-space (period > 225 minutes) satellites. tsince is   */
	/* time since epoch in minutes, tle is a pointer to a tle_t     */
	/* structure with Keplerian orbital elements and pos and vel    */
	/* are vector_t structures returning ECI satellite position and */
	/* velocity. Use Convert_Sat_State() to convert to km and km/s. */

	int i;

	static double x3thm1, c1, x1mth2, c4, xnodcf, t2cof, xlcof,
	aycof, x7thm1;

	double a, axn, ayn, aynl, beta, betal, capu, cos2u, cosepw, cosik,
	cosnok, cosu, cosuk, ecose, elsq, epw, esine, pl, theta4, rdot,
	rdotk, rfdot, rfdotk, rk, sin2u, sinepw, sinik, sinnok, sinu,
	sinuk, tempe, templ, tsq, u, uk, ux, uy, uz, vx, vy, vz, xinck, xl,
	xlt, xmam, xmdf, xmx, xmy, xnoddf, xnodek, xll, a1, a3ovk2, ao, c2,
	coef, coef1, x1m5th, xhdot1, del1, r, delo, eeta, eta, etasq,
	perigee, psisq, tsi, qoms24, s4, pinvsq, temp, tempa, temp1,
	temp2, temp3, temp4, temp5, temp6, bx, by, bz, cx, cy, cz;

	static deep_arg_t deep_arg;

	/* Initialization */

	if (isFlagClear(SDP4_INITIALIZED_FLAG))
	{
		SetFlag(SDP4_INITIALIZED_FLAG);

		/* Recover original mean motion (xnodp) and   */
		/* semimajor axis (aodp) from input elements. */
	  
		a1=pow(xke/tle->xno,tothrd);
		deep_arg.cosio=cos(tle->xincl);
		deep_arg.theta2=deep_arg.cosio*deep_arg.cosio;
		x3thm1=3*deep_arg.theta2-1;
		deep_arg.eosq=tle->eo*tle->eo;
		deep_arg.betao2=1-deep_arg.eosq;
		deep_arg.betao=sqrt(deep_arg.betao2);
		del1=1.5*ck2*x3thm1/(a1*a1*deep_arg.betao*deep_arg.betao2);
		ao=a1*(1-del1*(0.5*tothrd+del1*(1+134/81*del1)));
		delo=1.5*ck2*x3thm1/(ao*ao*deep_arg.betao*deep_arg.betao2);
		deep_arg.xnodp=tle->xno/(1+delo);
		deep_arg.aodp=ao/(1-delo);

		/* For perigee below 156 km, the values */
		/* of s and qoms2t are altered.         */
	  
		s4=s;
		qoms24=qoms2t;
		perigee=(deep_arg.aodp*(1-tle->eo)-ae)*xkmper;
	  
		if (perigee<156.0)
		{
			if (perigee<=98.0)
				s4=20.0;
			else
				s4=perigee-78.0;
	
			qoms24=pow((120-s4)*ae/xkmper,4);
			s4=s4/xkmper+ae;
		}

		pinvsq=1/(deep_arg.aodp*deep_arg.aodp*deep_arg.betao2*deep_arg.betao2);
		deep_arg.sing=sin(tle->omegao);
		deep_arg.cosg=cos(tle->omegao);
		tsi=1/(deep_arg.aodp-s4);
		eta=deep_arg.aodp*tle->eo*tsi;
		etasq=eta*eta;
		eeta=tle->eo*eta;
		psisq=fabs(1-etasq);
		coef=qoms24*pow(tsi,4);
		coef1=coef/pow(psisq,3.5);
		c2=coef1*deep_arg.xnodp*(deep_arg.aodp*(1+1.5*etasq+eeta*(4+etasq))+0.75*ck2*tsi/psisq*x3thm1*(8+3*etasq*(8+etasq)));
		c1=tle->bstar*c2;
		deep_arg.sinio=sin(tle->xincl);
		a3ovk2=-xj3/ck2*pow(ae,3);
		x1mth2=1-deep_arg.theta2;
		c4=2*deep_arg.xnodp*coef1*deep_arg.aodp*deep_arg.betao2*(eta*(2+0.5*etasq)+tle->eo*(0.5+2*etasq)-2*ck2*tsi/(deep_arg.aodp*psisq)*(-3*x3thm1*(1-2*eeta+etasq*(1.5-0.5*eeta))+0.75*x1mth2*(2*etasq-eeta*(1+etasq))*cos(2*tle->omegao)));
		theta4=deep_arg.theta2*deep_arg.theta2;
		temp1=3*ck2*pinvsq*deep_arg.xnodp;
		temp2=temp1*ck2*pinvsq;
		temp3=1.25*ck4*pinvsq*pinvsq*deep_arg.xnodp;
		deep_arg.xmdot=deep_arg.xnodp+0.5*temp1*deep_arg.betao*x3thm1+0.0625*temp2*deep_arg.betao*(13-78*deep_arg.theta2+137*theta4);
		x1m5th=1-5*deep_arg.theta2;
		deep_arg.omgdot=-0.5*temp1*x1m5th+0.0625*temp2*(7-114*deep_arg.theta2+395*theta4)+temp3*(3-36*deep_arg.theta2+49*theta4);
		xhdot1=-temp1*deep_arg.cosio;
		deep_arg.xnodot=xhdot1+(0.5*temp2*(4-19*deep_arg.theta2)+2*temp3*(3-7*deep_arg.theta2))*deep_arg.cosio;
		xnodcf=3.5*deep_arg.betao2*xhdot1*c1;
		t2cof=1.5*c1;
		xlcof=0.125*a3ovk2*deep_arg.sinio*(3+5*deep_arg.cosio)/(1+deep_arg.cosio);
		aycof=0.25*a3ovk2*deep_arg.sinio;
		x7thm1=7*deep_arg.theta2-1;

		/* initialize Deep() */

		Deep(dpinit,tle,&deep_arg);
	}

	/* Update for secular gravity and atmospheric drag */
	xmdf=tle->xmo+deep_arg.xmdot*tsince;
	deep_arg.omgadf=tle->omegao+deep_arg.omgdot*tsince;
	xnoddf=tle->xnodeo+deep_arg.xnodot*tsince;
	tsq=tsince*tsince;
	deep_arg.xnode=xnoddf+xnodcf*tsq;
	tempa=1-c1*tsince;
	tempe=tle->bstar*c4*tsince;
	templ=t2cof*tsq;
	deep_arg.xn=deep_arg.xnodp;

	/* Update for deep-space secular effects */
	deep_arg.xll=xmdf;
	deep_arg.t=tsince;

	Deep(dpsec, tle, &deep_arg);

	xmdf=deep_arg.xll;
	a=pow(xke/deep_arg.xn,tothrd)*tempa*tempa;
	deep_arg.em=deep_arg.em-tempe;
	xmam=xmdf+deep_arg.xnodp*templ;

	/* Update for deep-space periodic effects */
	deep_arg.xll=xmam;

	Deep(dpper,tle,&deep_arg);

	xmam=deep_arg.xll;
	xl=xmam+deep_arg.omgadf+deep_arg.xnode;
	beta=sqrt(1-deep_arg.em*deep_arg.em);
	deep_arg.xn=xke/pow(a,1.5);

	/* Long period periodics */
	axn=deep_arg.em*cos(deep_arg.omgadf);
	temp=1/(a*beta*beta);
	xll=temp*xlcof*axn;
	aynl=temp*aycof;
	xlt=xl+xll;
	ayn=deep_arg.em*sin(deep_arg.omgadf)+aynl;

	/* Solve Kepler's Equation */
	capu=FMod2p(xlt-deep_arg.xnode);
	temp2=capu;
	i=0;

	do
	{
		sinepw=sin(temp2);
		cosepw=cos(temp2);
		temp3=axn*sinepw;
		temp4=ayn*cosepw;
		temp5=axn*cosepw;
		temp6=ayn*sinepw;
		epw=(capu-temp4+temp3-temp2)/(1-temp5-temp6)+temp2;
	  
		if (fabs(epw-temp2)<=e6a)
			break;

		temp2=epw;
	  
	} while (i++<10);

	/* Short period preliminary quantities */
	ecose=temp5+temp6;
	esine=temp3-temp4;
	elsq=axn*axn+ayn*ayn;
	temp=1-elsq;
	pl=a*temp;
	r=a*(1-ecose);
	temp1=1/r;
	rdot=xke*sqrt(a)*esine*temp1;
	rfdot=xke*sqrt(pl)*temp1;
	temp2=a*temp1;
	betal=sqrt(temp);
	temp3=1/(1+betal);
	cosu=temp2*(cosepw-axn+ayn*esine*temp3);
	sinu=temp2*(sinepw-ayn-axn*esine*temp3);
	u=AcTan(sinu,cosu);
	sin2u=2*sinu*cosu;
	cos2u=2*cosu*cosu-1;
	temp=1/pl;
	temp1=ck2*temp;
	temp2=temp1*temp;

	/* Update for short periodics */
	rk=r*(1-1.5*temp2*betal*x3thm1)+0.5*temp1*x1mth2*cos2u;
	uk=u-0.25*temp2*x7thm1*sin2u;
	xnodek=deep_arg.xnode+1.5*temp2*deep_arg.cosio*sin2u;
	xinck=deep_arg.xinc+1.5*temp2*deep_arg.cosio*deep_arg.sinio*cos2u;
	rdotk=rdot-deep_arg.xn*temp1*x1mth2*sin2u;
	rfdotk=rfdot+deep_arg.xn*temp1*(x1mth2*cos2u+1.5*x3thm1);

	/* Orientation vectors */
	sinuk=sin(uk);
	cosuk=cos(uk);
	sinik=sin(xinck);
	cosik=cos(xinck);
	sinnok=sin(xnodek);
	cosnok=cos(xnodek);
	xmx=-sinnok*cosik;
	xmy=cosnok*cosik;
	ux=xmx*sinuk+cosnok*cosuk;
	uy=xmy*sinuk+sinnok*cosuk;
	uz=sinik*sinuk;
	vx=xmx*cosuk-cosnok*sinuk;
	vy=xmy*cosuk-sinnok*sinuk;
	vz=sinik*cosuk;

	/* Position and velocity */
	pos->x=rk*ux;
	pos->y=rk*uy;
	pos->z=rk*uz;
	vel->x=rdotk*ux+rfdotk*vx;
	vel->y=rdotk*uy+rfdotk*vy;
	vel->z=rdotk*uz+rfdotk*vz;

	/* Calculations for squint angle begin here... */

	if (calc_squint)
	{
		bx=cos(alat)*cos(alon+deep_arg.omgadf);
		by=cos(alat)*sin(alon+deep_arg.omgadf);
		bz=sin(alat);
		cx=bx;
		cy=by*cos(xinck)-bz*sin(xinck);
		cz=by*sin(xinck)+bz*cos(xinck);
		ax=cx*cos(xnodek)-cy*sin(xnodek);
		ay=cx*sin(xnodek)+cy*cos(xnodek);
		az=cz;
	}
	
	/* Phase in radians */
	phase=xlt-deep_arg.xnode-deep_arg.omgadf+twopi;
    
	if (phase<0.0)
		phase+=twopi;

	phase=FMod2p(phase);
}

void Calculate_User_PosVel(double time, geodetic_t *geodetic, vector_t *obs_pos, vector_t *obs_vel)
{
	/* Calculate_User_PosVel() passes the user's geodetic position
	   and the time of interest and returns the ECI position and
	   velocity of the observer.  The velocity calculation assumes
	   the geodetic position is stationary relative to the earth's
	   surface. */

	/* Reference:  The 1992 Astronomical Almanac, page K11. */

	double c, sq, achcp;

	geodetic->theta=FMod2p(ThetaG_JD(time)+geodetic->lon); /* LMST */
	c=1/sqrt(1+f*(f-2)*Sqr(sin(geodetic->lat)));
	sq=Sqr(1-f)*c;
	achcp=(xkmper*c+geodetic->alt)*cos(geodetic->lat);
	obs_pos->x=achcp*cos(geodetic->theta); /* kilometers */
	obs_pos->y=achcp*sin(geodetic->theta);
	obs_pos->z=(xkmper*sq+geodetic->alt)*sin(geodetic->lat);
	obs_vel->x=-mfactor*obs_pos->y; /* kilometers/second */
	obs_vel->y=mfactor*obs_pos->x;
	obs_vel->z=0;
	Magnitude(obs_pos);
	Magnitude(obs_vel);
}

void Calculate_LatLonAlt(double time, vector_t *pos,  geodetic_t *geodetic)
{
	/* Procedure Calculate_LatLonAlt will calculate the geodetic  */
	/* position of an object given its ECI position pos and time. */
	/* It is intended to be used to determine the ground track of */
	/* a satellite.  The calculations  assume the earth to be an  */
	/* oblate spheroid as defined in WGS '72.                     */

	/* Reference:  The 1992 Astronomical Almanac, page K12. */

	double r, e2, phi, c;

	geodetic->theta=AcTan(pos->y,pos->x); /* radians */
	geodetic->lon=FMod2p(geodetic->theta-ThetaG_JD(time)); /* radians */
	r=sqrt(Sqr(pos->x)+Sqr(pos->y));
	e2=f*(2-f);
	geodetic->lat=AcTan(pos->z,r); /* radians */

	do
	{
		phi=geodetic->lat;
		c=1/sqrt(1-e2*Sqr(sin(phi)));
		geodetic->lat=AcTan(pos->z+xkmper*c*e2*sin(phi),r);

	} while (fabs(geodetic->lat-phi)>=1E-10);

	geodetic->alt=r/cos(geodetic->lat)-xkmper*c; /* kilometers */

	if (geodetic->lat>pio2)
		geodetic->lat-=twopi;
}

void Calculate_Obs(double time, vector_t *pos, vector_t *vel, geodetic_t *geodetic, vector_t *obs_set)
{
	/* The procedures Calculate_Obs and Calculate_RADec calculate         */
	/* the *topocentric* coordinates of the object with ECI position,     */
	/* {pos}, and velocity, {vel}, from location {geodetic} at {time}.    */
	/* The {obs_set} returned for Calculate_Obs consists of azimuth,      */
	/* elevation, range, and range rate (in that order) with units of     */
	/* radians, radians, kilometers, and kilometers/second, respectively. */
	/* The WGS '72 geoid is used and the effect of atmospheric refraction */
	/* (under standard temperature and pressure) is incorporated into the */
	/* elevation calculation; the effect of atmospheric refraction on     */
	/* range and range rate has not yet been quantified.                  */

	/* The {obs_set} for Calculate_RADec consists of right ascension and  */
	/* declination (in that order) in radians.  Again, calculations are   */
	/* based on *topocentric* position using the WGS '72 geoid and        */
	/* incorporating atmospheric refraction.                              */

	double sin_lat, cos_lat, sin_theta, cos_theta, el, azim, top_s, top_e, top_z;

	vector_t obs_pos, obs_vel, range, rgvel;

	Calculate_User_PosVel(time, geodetic, &obs_pos, &obs_vel);

	range.x=pos->x-obs_pos.x;
	range.y=pos->y-obs_pos.y;
	range.z=pos->z-obs_pos.z;

	/* Save these values globally for calculating squint angles later... */

	rx=range.x;
	ry=range.y;
	rz=range.z;

	rgvel.x=vel->x-obs_vel.x;
	rgvel.y=vel->y-obs_vel.y;
	rgvel.z=vel->z-obs_vel.z;

	Magnitude(&range);

	sin_lat=sin(geodetic->lat);
	cos_lat=cos(geodetic->lat);
	sin_theta=sin(geodetic->theta);
	cos_theta=cos(geodetic->theta);
	top_s=sin_lat*cos_theta*range.x+sin_lat*sin_theta*range.y-cos_lat*range.z;
	top_e=-sin_theta*range.x+cos_theta*range.y;
	top_z=cos_lat*cos_theta*range.x+cos_lat*sin_theta*range.y+sin_lat*range.z;
	azim=atan(-top_e/top_s); /* Azimuth */

	if (top_s>0.0) 
		azim=azim+pi;

	if (azim<0.0)
		azim=azim+twopi;

	el=ArcSin(top_z/range.w);
	obs_set->x=azim;	/* Azimuth (radians)   */
	obs_set->y=el;		/* Elevation (radians) */
	obs_set->z=range.w;	/* Range (kilometers)  */

	/* Range Rate (kilometers/second) */

	obs_set->w=Dot(&range,&rgvel)/range.w;

	/* Corrections for atmospheric refraction */
	/* Reference:  Astronomical Algorithms by Jean Meeus, pp. 101-104    */
	/* Correction is meaningless when apparent elevation is below horizon */

	/*** The following adjustment for
		 atmospheric refraction is bypassed ***/

	/* obs_set->y=obs_set->y+Radians((1.02/tan(Radians(Degrees(el)+10.3/(Degrees(el)+5.11))))/60); */

	obs_set->y=el;

	/**** End bypass ****/

	if (obs_set->y>=0.0)
		SetFlag(VISIBLE_FLAG);
	else
	{
		obs_set->y=el;  /* Reset to true elevation */
		ClearFlag(VISIBLE_FLAG);
	}
}

void Calculate_RADec(double time, vector_t *pos, vector_t *vel, geodetic_t *geodetic, vector_t *obs_set)
{
	/* Reference:  Methods of Orbit Determination by  */
	/*             Pedro Ramon Escobal, pp. 401-402   */

	double	phi, theta, sin_theta, cos_theta, sin_phi, cos_phi, az, el,
		Lxh, Lyh, Lzh, Sx, Ex, Zx, Sy, Ey, Zy, Sz, Ez, Zz, Lx, Ly,
		Lz, cos_delta, sin_alpha, cos_alpha;

	Calculate_Obs(time,pos,vel,geodetic,obs_set);

	az=obs_set->x;
	el=obs_set->y;
	phi=geodetic->lat;
	theta=FMod2p(ThetaG_JD(time)+geodetic->lon);
	sin_theta=sin(theta);
	cos_theta=cos(theta);
	sin_phi=sin(phi);
	cos_phi=cos(phi);
	Lxh=-cos(az)*cos(el);
	Lyh=sin(az)*cos(el);
	Lzh=sin(el);
	Sx=sin_phi*cos_theta;
	Ex=-sin_theta;
	Zx=cos_theta*cos_phi;
	Sy=sin_phi*sin_theta;
	Ey=cos_theta;
	Zy=sin_theta*cos_phi;
	Sz=-cos_phi;
	Ez=0.0;
	Zz=sin_phi;
	Lx=Sx*Lxh+Ex*Lyh+Zx*Lzh;
	Ly=Sy*Lxh+Ey*Lyh+Zy*Lzh;
	Lz=Sz*Lxh+Ez*Lyh+Zz*Lzh;
	obs_set->y=ArcSin(Lz);  /* Declination (radians) */
	cos_delta=sqrt(1.0-Sqr(Lz));
	sin_alpha=Ly/cos_delta;
	cos_alpha=Lx/cos_delta;
	obs_set->x=AcTan(sin_alpha,cos_alpha); /* Right Ascension (radians) */
	obs_set->x=FMod2p(obs_set->x);
}

/* .... SGP4/SDP4 functions end .... */

char *strncpy2(char *dest, const char *src, unsigned n)
{
	unsigned i;

	for (i=0; i<n && src[i]!=0; i++)
		dest[i]=src[i];

	dest[i]=0;

	return dest;
}

void bailout (char *string)
{
	/* This function quits ncurses, resets and "beeps"
	   the terminal, and displays an error message (string)
	   when we need to bail out of the program in a hurry. */

	beep();	
	curs_set(1);
	bkgdset(COLOR_PAIR(1));
	clear();
	refresh();
	endwin();
	fprintf(stderr,"\n*** predict: %s!\n",string);
}

char file_id(char *filename)
{
	/* Returns a numeric code based on the file
	   type given by the filename extension, where
	   .tle=1, .csv=2, .kvn=3, ,xml=4, .json=5
	   Returns a 0 if no match to the extension
	   is found. Evaluations are case insensitive. */
	
	char *dot, result=0;

	dot=strrchr(filename, '.');

	if (dot!=NULL)
	{	

		if (strncasecmp(dot,".tle",4)==0)
			result=1;

		else if (strncasecmp(dot,".csv",4)==0)
			result=2;

		else if (strncasecmp(dot,".kvn",4)==0)
			result=3;

		else if (strncasecmp(dot,".xml",4)==0)
			result=4;

		else if (strncasecmp(dot,".json",5)==0)
			result=5;
	}

	return result;
}

unsigned long file_age(char *filename)
{
	/* Returns the number of seconds since the time
	   of the specified file's last modification. */

	struct stat file_info;
	time_t mod_time;
	unsigned long file, now, age=0L;

	if (stat(filename, &file_info)==0)
	{
		mod_time=file_info.st_mtime;
		file=(unsigned long)mod_time;
		now=(unsigned long)time(NULL);
		age=now-file;
	}

	return age;
}

void TrackDataNet(int sockd, double elevation, double azimuth)
{
	int len;
	char message[20]={"0"};

	/* Send AZ/EL data to rotctld.  If positions are sent too
	   often, rotctld will queue them and the antenna will lag
	   behind. Therefore, we wait for confirmation from last
	   command before sending the next. */

	while (recv(sockd, message, sizeof(message), MSG_DONTWAIT) < 1);

	snprintf(message, 20, "P %.2f %.2f\n", azimuth, elevation);
	len=strlen(message);

	if (send(sockd, message, len, 0)!=len)
	{
		bailout("Communications failure with rotctld");
		exit(-1);
	}
}

int passivesock(char *service, char *protocol, int qlen)
{
	/* This function opens the socket port */

	struct servent *pse;
	struct protoent *ppe;
	struct sockaddr_in sin;
	int sd, type;
	
	memset((char *)&sin, 0, sizeof(struct sockaddr_in));
	sin.sin_family=AF_INET;
	sin.sin_addr.s_addr=INADDR_ANY;
	
	if ((pse=getservbyname(service,protocol)))
		sin.sin_port=htons(ntohs((unsigned short)pse->s_port)+portbase);

	else if ((sin.sin_port=htons((unsigned short)atoi(service)))==0)
	{
		bailout("Can't get service");
		exit(-1);
	}
	
	if ((ppe=getprotobyname(protocol))==0)
	{
		bailout("Can't get protocol");
		exit(-1);
	}
	
	if (strcmp(protocol,"udp")==0)
		type=SOCK_DGRAM;
	else
		type=SOCK_STREAM;
	
	sd=socket(PF_INET,type, ppe->p_proto);

	if (sd<0)
	{
		bailout("Can't open socket");
		exit(-1);
	}
	
	if (bind(sd,(struct sockaddr *)&sin,sizeof(sin))<0)
	{
		bailout("Can't bind");
		exit(-1);
	}

	return sd;
}

void socket_server(char *predict_name)
{
	/* This is the socket server code contributed by
	   Ivan Galysh, KD4HBO on 10-Jan-2000 with modifications
	   made by Angelou Dimitrios, SV9TAT (contact@angeloudi.com)
	   on 23-May-2023 to harden against remote code execution. */

	int i, j, n, sock, child_status, child2_status;
	socklen_t alen;
	struct sockaddr_in fsin;
	char buf[80], buff[1000], satname[50], tempname[30], ok;
	time_t t;
	long nxtevt;
	pid_t child_pid, child2_pid, wpid, wpid2;
	FILE *fd=NULL;

	/* Open a socket port at "predict" or netport if defined */

	if (netport[0]==0)
		strncpy2(netport,"predict\0",8);

	sock=passivesock(netport,"udp",10);
 	alen=sizeof(fsin);
	
	/* This is the main loop for monitoring the socket
	   port and sending back replies to clients */

	while (1)
	{
		/* Get datagram from socket port */
		
		if ((n=recvfrom(sock,buf,sizeof(buf),0,(struct sockaddr *)&fsin,&alen)) < 0)
			exit (-1);

		buf[n]=0;
		ok=1;

		child_pid=fork();

		if (child_pid==0)
		{
			/* Parse the command in the datagram */

			if ((strncmp("GET_SAT",buf,7)==0) && (strncmp("GET_SAT_POS",buf,11)!=0))
			{
				/* Parse "buf" for satellite name */

				for (i=0; buf[i]!=32 && buf[i]!=0 && i<39; i++);
	
				for (j=++i; buf[j]!='\n' && buf[j]!=0 && (j-i)<25; j++)
					satname[j-i]=buf[j];
	
				satname[j-i]=0;
	
				/* Do a simple search for the matching satellite name */
	
				for (i=0; i<24; i++)
				{
					if ((strncmp(satname,sat[i].name,25)==0) || (atol(satname)==sat[i].catnum))
					{
						nxtevt=(long)rint(86400.0*(nextevent[i]));
	
						/* Build text buffer with satellite data */
						sprintf(buff,"%s\n%-7.2f\n%+-6.2f\n%-7.2f\n%+-6.2f\n%ld\n%-7.2f\n%-7.2f\n%-7.2f\n%-7.2f\n%u\n%c\n%-7.2f\n%-7.2f\n%-7.2f\n",sat[i].name,long_array[i],lat_array[i],az_array[i],el_array[i],nxtevt,footprint_array[i],range_array[i],altitude_array[i],velocity_array[i],orbitnum_array[i],visibility_array[i],phase_array[i],eclipse_depth_array[i],squint_array[i]);
	
						/* Send buffer back to the client that sent the request */
						sendto(sock,buff,strlen(buff),0,(struct sockaddr*)&fsin,sizeof(fsin));
						ok=0;
						break;
					}
				}
			}
	
			if (strncmp("GET_TLE",buf,7)==0 || strncmp("GET_KEP",buf,7)==0)
			{
				/* Parse "buf" for satellite name */

				for (i=0; buf[i]!=32 && buf[i]!=0 && i<39; i++);
	
				for (j=++i; buf[j]!='\n' && buf[j]!=0 && (j-i)<25; j++)
					satname[j-i]=buf[j];
	
				satname[j-i]=0;
	
				/* Do a simple search for the matching satellite name */
	
				for (i=0; i<24; i++)
				{
					if ((strncmp(satname,sat[i].name,25)==0) || (atol(satname)==sat[i].catnum))
					{
						/* Build text buffer with satellite data */
	
						sprintf(buff,"%s",sat[i].text);

						/* Send buffer back to the client that sent the request */

						sendto(sock,buff,strlen(buff),0,(struct sockaddr*)&fsin,sizeof(fsin));
						ok=0;
						break;
					}
				}
			}
	
			if (strncmp("GET_DOPPLER",buf,11)==0)
			{
				/* Parse "buf" for satellite name */

				for (i=0; buf[i]!=32 && buf[i]!=0 && i<39; i++);
	
				for (j=++i; buf[j]!='\n' && buf[j]!=0 && (j-i)<25; j++)
					satname[j-i]=buf[j];
	
				satname[j-i]=0;
	
				/* Do a simple search for the matching satellite name */
	
				for (i=0; i<24; i++)
				{
					if ((strncmp(satname,sat[i].name,25)==0) || (atol(satname)==sat[i].catnum))
					{
						/* Get Normalized (100 MHz)
						Doppler shift for sat[i] */
	
						sprintf(buff,"%f\n",doppler[i]);
	
						/* Send buffer back to client who sent request */

						sendto(sock,buff,strlen(buff),0,(struct sockaddr*)&fsin,sizeof(fsin));
						ok=0;
						break;
					}
				}
			}
	
			if (strncmp("GET_LIST",buf,8)==0)
			{
				buff[0]=0;
	
				for (i=0; i<24; i++)
				{
					if (sat[i].name[0]!=0)
						strcat(buff,sat[i].name);
	
					strcat(buff,"\n");
				}
	
				sendto(sock,buff,strlen(buff),0,(struct sockaddr *)&fsin,sizeof(fsin));
				ok=0;
			}
	
			if (strncmp("RELOAD_TLE",buf,10)==0 || strncmp("RELOAD_KEP",buf,10)==0)
			{
				buff[0]=0;
				sendto(sock,buff,strlen(buff),0,(struct sockaddr *)&fsin,sizeof(fsin));
				reload_kep=1;
				ok=0;
			}
	
			if (strncmp("GET_SUN",buf,7)==0)
			{
				buff[0]=0;
				sprintf(buff,"%-7.2f\n%+-6.2f\n%-7.2f\n%-7.2f\n%-7.2f\n",sun_azi, sun_ele, sun_lat, sun_lon, sun_ra);
				sendto(sock,buff,strlen(buff),0,(struct sockaddr *)&fsin,sizeof(fsin));
				ok=0;
			}
	
			if (strncmp("GET_MOON",buf,8)==0)
			{
				buff[0]=0;
				sprintf(buff,"%-7.2f\n%+-6.2f\n%-7.2f\n%-7.2f\n%-7.2f\n",moon_az, moon_el, moon_dec, moon_gha, moon_ra);
				sendto(sock,buff,strlen(buff),0,(struct sockaddr *)&fsin,sizeof(fsin));
				ok=0;
			}
	
			if (strncmp("GET_MODE",buf,8)==0)
			{
				sendto(sock,tracking_mode,strlen(tracking_mode),0,(struct sockaddr *)&fsin,sizeof(fsin));
				ok=0;
			}
	
			if (strncmp("GET_VERSION",buf,11)==0)
			{
				buff[0]=0;
				sprintf(buff,"%s\n",PREDICT_VERSION);
				sendto(sock,buff,strlen(buff),0,(struct sockaddr *)&fsin,sizeof(fsin));
				ok=0;
			}
	
			if (strncmp("GET_QTH",buf,7)==0)
			{
				buff[0]=0;
				sprintf(buff,"%s\n%g\n%g\n%d\n",qth.callsign, qth.stnlat, qth.stnlong, qth.stnalt);
				sendto(sock,buff,strlen(buff),0,(struct sockaddr *)&fsin,sizeof(fsin));
				ok=0;
			}
	
			if (strncmp("GET_TIME$",buf,9)==0)
			{
				buff[0]=0;
				t=time(NULL);
				sprintf(buff,"%s",asctime(gmtime(&t)));
	
				if (buff[8]==32)
					buff[8]='0';
	
				sendto(sock,buff,strlen(buff),0,(struct sockaddr *)&fsin,sizeof(fsin));
				buf[0]=0;
				ok=0;
			}
	
			if (strncmp("GET_TIME",buf,8)==0)
			{
				buff[0]=0;
				t=time(NULL);
				sprintf(buff,"%lu\n",(unsigned long)t);
				sendto(sock,buff,strlen(buff),0,(struct sockaddr *)&fsin,sizeof(fsin));
				ok=0;
			}
	
			if (strncmp("GET_SAT_POS",buf,11)==0)
			{
				/* Parse "buf" for satellite name and arguments */

				for (i=0; buf[i]!=32 && buf[i]!=0 && i<39; i++);
	
				for (j=++i; buf[j]!='\n' && buf[j]!=0 && (j-i)<49; j++)
					satname[j-i]=buf[j];
	
				satname[j-i]=0;
	
				/* Send request to predict with output
				directed to a temporary file under /tmp */
	
				strcpy(tempname,"/tmp/XXXXXX\0");
				i=mkstemp(tempname);

				pid_t child2_pid, wpid2;
				int child2_status;
				child2_pid=fork();

				if (child2_pid==0)
					execlp(predict_name, predict_name, "-f", satname, "-t", kepfile, "-q", qthfile, "-o", tempname, NULL);	
				else
				{
					while ((wpid2=wait(&child2_status))>0);
		
					/* Append an EOF marker (CNTRL-Z) to the end of file */
		
					fd=fopen(tempname,"a");

					if (fd!=NULL)
					{
						fprintf(fd,"%c\n",26);  /* Control-Z */
						fclose(fd);
					}
		
					buff[0]=0;
		
					/* Send the file to the client */
		
					fd=fopen(tempname,"rb");

					if (fd!=NULL)
					{
						fgets(buff,80,fd);
		
						do
						{
							sendto(sock,buff,strlen(buff),0,(struct sockaddr *)&fsin,sizeof(fsin));
							fgets(buff,80,fd);

							/* usleep(2);  if needed (for flow-control) */
		
						} while (feof(fd)==0);

						fclose(fd);
					}

					unlink(tempname);
					close(i);
					ok=0;
				}
			}
	
			if (strncmp("PREDICT",buf,7)==0)
			{
				/* Parse "buf" for satellite name and arguments */

				for (i=0; buf[i]!=32 && buf[i]!=0 && i<39; i++);
	
				for (j=++i; buf[j]!='\n' && buf[j]!=0 && (j-i)<49; j++)
					satname[j-i]=buf[j];
	
				satname[j-i]=0;
	
				/* Send request to predict with output
				directed to a temporary file under /tmp */
	
				strcpy(tempname,"/tmp/XXXXXX\0");
				i=mkstemp(tempname);
	
				child2_pid=fork();

				if (child2_pid==0)
					execlp(predict_name, predict_name, "-p", satname, "-t", kepfile, "q", qthfile, "-o", tempname, NULL);	

				else
				{
					while ((wpid2=wait(&child2_status))>0);
	
					/* Append an EOF marker (CNTRL-Z) to the end of file */
		
					fd=fopen(tempname,"a");

					if (fd!=NULL)
					{
						fprintf(fd,"%c\n",26);  /* Control-Z */
						fclose(fd);
					}
		
					buff[0]=0;
		
					/* Send the file to the client */
		
					fd=fopen(tempname,"rb");

					if (fd!=NULL)
					{
						fgets(buff,80,fd);
		
						do
						{
							sendto(sock,buff,strlen(buff),0,(struct sockaddr *)&fsin,sizeof(fsin));
							fgets(buff,80,fd);

							/* usleep(2);  if needed (for flow-control) */
		
						} while (feof(fd)==0);
		
						fclose(fd);
					}

					unlink(tempname);
					close(i);
					ok=0;
				}
			}
			exit(ok);	
		}

		else
		{
			while((wpid = wait(&child_status)) > 0);

			if (child_status!=0)
				sendto(sock,"Huh?\n",5,0,(struct sockaddr *)&fsin,sizeof(fsin));
		}
	} 	
}

void Banner (void)
{
	curs_set(0);
	bkgdset(COLOR_PAIR(3));
	clear();

	attroff(COLOR_PAIR(3));

	attrset(COLOR_PAIR(5)|A_BOLD);
	mvprintw(3,18,"                                           ");
	mvprintw(4,18,"         --== PREDICT  v%s ==--         ",PREDICT_VERSION);
	mvprintw(5,18,"   Released by John A. Magliacane, KD2BD   ");
	mvprintw(6,18,"                 July 2026                 ");
	mvprintw(7,18,"                                           ");
}

void AnyKey (void)
{
	int ch, button1=0, button3=0, xit=0;

	nodelay(stdscr,FALSE);
	mvprintw(23,24,"<< Press Any Key To Continue >>");
	refresh();

	do
	{
		ch=getch();

		if (ch==KEY_MOUSE)
		{
			getmouse(&event);

			button1=(event.bstate&BUTTON1_CLICKED || event.bstate&BUTTON1_DOUBLE_CLICKED || event.bstate&BUTTON1_PRESSED || event.bstate&BUTTON1_RELEASED);
			button3=(event.bstate&BUTTON3_CLICKED || event.bstate&BUTTON3_DOUBLE_CLICKED || event.bstate&BUTTON3_PRESSED || event.bstate&BUTTON3_RELEASED);

			if (button1)
			{
				if (event.y==23 && event.x>=24 && event.x<=54)
					xit=1;
			}

			if (button3)
				xit=1;
		}

		else if (ch<128)
			xit=1;

	} while (xit==0);
}

double FixAngle (double x)
{
	/* This function reduces angles greater than
	   two pi by subtracting two pi from the angle */

	while (x>twopi)
		x-=twopi;

	return x;
}

double PrimeAngle (double x)
{
	/* This function is used in the FindMoon() function. */

	x=x-360.0*floor(x/360.0);
	return x;
}

char *subString (char *string, int start, int end)
{
	/* This function returns a substring based on the starting
	   and ending positions provided.  Used heavily in the
	   autoUpdate function when parsing 2-line element data,
	   it doesn't copy spaces. */

	unsigned x, y;
	char static temp[80];

	temp[0]=0;

	if (end>=start)
	{
		for (x=start, y=0; x<=end && string[x]!=0 && y<78; x++)
			if (string[x]!=' ')
			{
				temp[y]=string[x];
				y++;
			}

		temp[y]=0;
	}

	return temp;
}

char *Abbreviate (char *string, int n)
{
	/* This function returns an abbreviated substring of the original,
	   including a '~' character if a non-blank character is chopped
	   out of the generated substring.  n is the length of the desired
	   substring.  It is used for abbreviating satellite names. */

	strncpy2(temp,string,78);

	if (temp[n]!=0 && temp[n]!=32)
	{
		temp[n-2]='~';
		temp[n-1]=temp[strlen(temp)-1];
	}

	temp[n]=0;

	return temp;
}

unsigned doy(unsigned month, unsigned day, unsigned year)
{
	/* Returns the day of the year given the month (1-12), day, and
	   4-digit year.  January 1 = day 1.  Invalid dates return 0. */

	unsigned daynum=0;
	const unsigned offset[]={0,31,59,90,120,151,181,212,243,273,304,335};

	if (month<=12)
	{
		daynum=day+offset[month-1];

		if (((year%4==0) && (year%100!=0)) || ((year%400==0) && (month>2)))
			daynum++;
	}

	return daynum;
}

double string2epoch(char *string)
{
	/* Parse the Date and Time string */
	/* of the form: 2021-01-21T19:48:48.875616 */
	/* and return the fractional day number of the year */

	unsigned z, month, day, year, hours, minutes, daynumber;
	double epoch=0.0, seconds;

	/* The character [T] is used as a designator to indicate */
	/* the start of the representation of the time of day */
	/* component in these ISO-8601 expressions.  However, some */
	/* sources fail to include this designator, so we'll insert one. */

	string[10]='T';

	z=sscanf(string,"%4u-%2u-%2uT%2u:%2u:%lf",&year,&month,&day,&hours,&minutes,&seconds);

	if (z==6)
	{
		daynumber=doy(month,day,year);
		epoch=(double)daynumber+(hours/24.0)+(minutes/1440.0)+(seconds/86400.0);
	}

	return epoch;
}

char *epoch2string(unsigned year, double dayfloat)
{
	/* Generates a string specifying the date in ISO8601 format
	   such as 2026-05-22T19:21:57.865415 as would be obtained
	   using: date --utc +%FT%T.%6N */

	unsigned offset[]={0,31,59,90,120,151,181,212,243,273,304,335}, day, month;
	double dayfraction, daynum, hours=0.0, minutes=0.0, seconds=0.0;
	static char result[30];

	/* Modify the "offset" lookup table if it's a leap year */

	if (((year%4==0) && (year%100!=0)) || (year%400==0))
		for (month=2; month<12; month++)
			offset[month]++;

	dayfraction=24.0*modf(dayfloat, &daynum);
	dayfraction=60.0*modf(dayfraction, &hours);
	seconds=60.0*modf(dayfraction, &minutes);

	for (month=0; month<12 && offset[month]<=daynum; month++);

	day=daynum-offset[month-1];

	snprintf(result,30,"%4u-%02u-%02uT%02.0lf:%02.0lf:%010.7lf%c",year, month, day, hours, minutes, seconds,0);

	return result;
}

char tle_check (char *line1, char *line2)
{
	/* This function scans line 1 and line 2 of a NASA 2-Line element
	   set and returns a 1 if the element set appears to be valid or
	   a 0 if it does not.  If the data survives this torture test,
	   it's a pretty safe bet we're looking at a valid 2-line
	   element set and not just some random text that might pass
	   as orbital data based on a simple checksum calculation alone. */

	int x;
	unsigned sum1, sum2;

	/* Compute checksum for each line */

	for (x=0, sum1=0, sum2=0; x<=67; sum1+=val[(int)line1[x]], sum2+=val[(int)line2[x]], x++);

	/* Perform a "torture test" on the data */

	x=(val[(int)line1[68]]^(sum1%10)) | (val[(int)line2[68]]^(sum2%10)) |
	  (line1[0]^'1')  | (line1[1]^' ')  | (line1[7]^'U')  |
	  (line1[8]^' ')  | (line1[17]^' ') | (line1[23]^'.') |
	  (line1[32]^' ') | (line1[34]^'.') | (line1[43]^' ') |
	  (line1[52]^' ') | (line1[61]^' ') | (line1[62]^'0') |
	  (line1[63]^' ') | (line2[0]^'2')  | (line2[1]^' ')  |
	  (line2[7]^' ')  | (line2[11]^'.') | (line2[16]^' ') |
	  (line2[20]^'.') | (line2[25]^' ') | (line2[33]^' ') |
	  (line2[37]^'.') | (line2[42]^' ') | (line2[46]^'.') |
	  (line2[51]^' ') | (line2[54]^'.') | (line1[2]^line2[2]) |
	  (line1[3]^line2[3]) | (line1[4]^line2[4]) |
	  (line1[5]^line2[5]) | (line1[6]^line2[6]) |
	  (isdigit(line1[68]) ? 0 : 1) | (isdigit(line2[68]) ? 0 : 1) |
	  (isdigit(line1[18]) ? 0 : 1) | (isdigit(line1[19]) ? 0 : 1) |
	  (isdigit(line2[31]) ? 0 : 1) | (isdigit(line2[32]) ? 0 : 1);

	return (x ? 0 : 1);
}

void keptext2data (int x)
{
	/* Updates the Keplerian data in the "sat" structure
	   based on the "text" string stored in the structure.
	   Opposite of data2keptext(). */

	unsigned y, z;
	char value[55]; 
	
	/* Satellite Name */

	for (z=0, y=0; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	strncpy2(sat[x].name,value,25);


	/* Catalog Number */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	sat[x].catnum=(unsigned)atoi(value);


	/* International Designator */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	strncpy2(sat[x].designator,value,14);


	/* Reference Epoch */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	sat[x].refepoch=string2epoch(value);
	value[4]=0;  /* Truncates epoch to year only (first 4 characters) */
	sat[x].year=(unsigned)atoi(value);


	/* Inclination */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	sat[x].incl=atof(value);


	/* Right Ascension of the Ascending Node */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	sat[x].raan=atof(value);


	/* Eccentricity */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	sat[x].eccn=atof(value);


	/* Argument of Perigee */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	sat[x].argper=atof(value);


	/* Mean Anomaly */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	sat[x].meanan=atof(value);


	/* Mean Motion */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	sat[x].meanmo=atof(value);


	/* Decay Rate */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	sat[x].drag=atof(value);


	/* NDDOT/6 */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	sat[x].nddot6=atof(value);


	/* BSTAR */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	sat[x].bstar=atof(value);


	/* Revolution Number at Epoch */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
		value[z]=sat[x].text[y];

	value[z]=0;

	sat[x].orbitnum=(unsigned)atoi(value);


	/* Element Set Number */

	for (z=0, y++; sat[x].text[y]!='\n' && sat[x].text[y]!=0 && y<255; y++, z++)
				value[z]=sat[x].text[y];

	value[z]=0;

	sat[x].setnum=(unsigned)atoi(value);
}

void data2keptext(int x)
{
	/* This function reads the orbital data held in the numeric
	   portion of the sat structure to ASCII and places the
	   result as a single '\n' delimited string in the "text"
	   portion of the structure.  Opposite of keptext2data(). */

	char string[255]={0};

	snprintf(string,254,"%s\n%u\n%s\n%u %.7lf\n%.7lf\n%.7lf\n%.7lf\n%.7lf\n%.7lf\n%.7lf\n%e\n%e\n%e\n%u\n%u\n",sat[x].name, sat[x].catnum, sat[x].designator, sat[x].year,  sat[x].refepoch, sat[x].incl, sat[x].raan, sat[x].eccn, sat[x].argper, sat[x].meanan, sat[x].meanmo, sat[x].drag, sat[x].nddot6, sat[x].bstar, sat[x].orbitnum, sat[x].setnum);

	strncpy2(sat[x].text, string, 254);
}

int network_check(void)
{
	/* Verify we have a network connection by connecting to
	   celesTrak.  Returns a 1 for success, 0 for failure. */

	CURL *curl;
	CURLcode result=0;

	/* Initialize the curl global state */
	curl_global_init(CURL_GLOBAL_DEFAULT);
    
	/* Get a curl easy handle */
	curl=curl_easy_init();

	if (curl)
	{
		/* Set a URL for the test */
		curl_easy_setopt(curl, CURLOPT_URL, "http://www.celestrak.org/");

		/* Don't write output to stdout */
		curl_easy_setopt(curl, CURLOPT_NOBODY, 1L);
        
		/* Timeout if connection takes longer than 5 seconds */
		curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);

		/* Perform the request */
		result=curl_easy_perform(curl);

		/* Clean up the handle */
		curl_easy_cleanup(curl);
	}

	/* Clean up global curl functions */
	curl_global_cleanup();

	/* Return the result */
	return ((result==CURLE_OK)? 1:0);
}

size_t write_data(void *ptr, size_t size, size_t nmemb, FILE *stream)
{
	/* Used by curl_download(). Directs downloaded
	   data to the specified stream (file) */

	return fwrite(ptr, size, nmemb, stream);
}

int curl_download(char *url, char *outfilename)
{
	/* Download a file from the specified "url" saving
	   the contents to the specified "outfilename". */
 
	CURL *curl;
	CURLcode result=0;
	int connectivity=0, failure_code=0;

	FILE *fd=NULL;
  
	/* Check for network connectivity to prevent
	   clobbering previously downloaded .csv files.  */

	connectivity=network_check(); 

	curl_global_init(CURL_GLOBAL_DEFAULT);
	curl=curl_easy_init();

	if (curl && connectivity)
	{
		fd=fopen(outfilename, "w");

		if (fd!=NULL)
		{
			curl_easy_setopt(curl, CURLOPT_URL, url);
			curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);
			curl_easy_setopt(curl, CURLOPT_WRITEDATA, fd);
			curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
			curl_easy_setopt(curl, CURLOPT_USERAGENT, "libcurl-agent/1.0");

			/* 1L enables verification of the server's SSL certificate */
			curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L);

			/* 2L verifies that the server name matches the certificate common name */
			curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 2L);

			/* Specify a custom CA certificate bundle path if your system lacks standard certificates: */
			/* curl_easy_setopt(curl, CURLOPT_CAINFO, "/path/to/cacert.pem"); */

			result=curl_easy_perform(curl);

			if (result!=CURLE_OK)
				failure_code--;

			curl_easy_cleanup(curl);

			fclose(fd);
		}

		else
			failure_code--;
	}

	curl_global_cleanup();

	return failure_code;
}

char *noradEvalue (double value)
{
	/* Converts numeric values to E notation used in NORAD TLEs */

	char string[15];

	snprintf(string,15,"%11.4e",value*10.0);

	output[0]=string[0];
	output[1]=string[1];
	output[2]=string[3];
	output[3]=string[4];
	output[4]=string[5];
	output[5]=string[6];
	output[6]='-';
	output[7]=string[10];
	output[8]=0;

	return output;
}

char *getJSONkeyword(char *string)
{
	/* Returns the keyword before the colon (:) in */
	/* "string" minus any double quotes or commas. */

	unsigned x, y;
	static char result[50];

	for (x=0, y=0; x<49 && string[x]!=0 && string[x]!=':'; x++)
	{
		if (isalnum(string[x]) || string[x]=='.' || string[x]=='_')
		{
			result[y]=string[x];
			y++;
		}
	}

	result[y]=0;

	return result;
}

char *getJSONvalue(char *string)
{
	/* Returns the value following the colon (:) in "string" */
	/* minus any double quotes, commas, ending curly braces, */
	/* square brackets, or leading spaces. */

	unsigned x, y=0;
	static char result[50];

	/* Find the first colon */

	for (x=0; string[x]!=0 && string[x]!=':'; x++);

	/* Ignore any spaces following the colon */

	while (string[x+1]==' ')
		x++;

	/* Build the return string beginning at this point */

	if (string[x]==':' || string[x]==' ')
	{
		for (x++, y=0; y<49 && string[x]!=0 && string[x]!='\n'; x++)
		{
			if (string[x]!='"' && string[x]!=',' && string[x]!='}' && string[x]!=']')
			{
				result[y]=string[x];
				y++;
			}
		}
	}

	result[y]=0;

	return result;
}

char *beforeEquals(char *string)
{
	/* Given a string such as: OBJECT_ID      = 1984-021B
	   Returns the substring preceding the first whitespace
	   character in parameter "string".  Used when parsing
	   KVN files. */

	unsigned x;
	static char result[20];

	for (x=0; x<19 && isspace(string[x])==0; x++)
		result[x]=string[x];

	result[x]=0;	

	return result;
}

char *afterEquals(char *string)
{
	/* Given a string such as: OBJECT_ID      = 1984-021B
	   Returns the substring that follows "= " in parameter
	   "string".  Used when parsing KVN files. */

	static char result[30];
	unsigned char x, y=0;

	/* Find the position of the '=' character */

	for (x=0; string[x]!='=' && string[x]!=0; x++);

	/* Copy the remainder of the string into "result" */

	if (string[x]!=0)
	{
		for (y=0; string[x]!=0 && y<29; y++, x++)
		{
			/* Don't copy EOL's into the result string */

			if (string[x+2]!=10 && string[x+2]!=13)
				result[y]=string[x+2];
			else
				result[y]=0;
		}
	}

	result[y]=0;
	
	return result;
}

void removeLeadingSpaces(char* str)
{
	if (str!=NULL && *str!='\0')
	{
		char* firstNonSpace=str;
    
		/* Find the first non-whitespace character. */

		while (*firstNonSpace!='\0' && isspace((unsigned char)*firstNonSpace))
			firstNonSpace++;

		/* If there were leading spaces, shift the string. */

		if (firstNonSpace!=str)
		{
			/* Calculate the length including the null terminator. */

			size_t len=strlen(firstNonSpace)+1;
			memmove(str, firstNonSpace, len);
		}
	}
}

double ReadBearing(char *input)
{
	/* This function takes numeric input in the form of a character
	   string, and returns an equivalent bearing in degrees as a
	   decimal number (double).  The input may either be expressed
	   in decimal format (40.139722) or degree, minute, second
	   format (40 08 23).  This function also safely handles
	   extra spaces found either leading, trailing, or
	   embedded within the numbers expressed in the
	   input string.  Decimal seconds are permitted. */
 
	double	degrees=0.0, minutes=0.0, seconds=0.0, bearing=0.0;
	int	count;

	/* Count number of arguments in the input
	   string and handle each accordingly. */

	count=sscanf(input, "%lf %lf %lf", &degrees, &minutes, &seconds);

	if (count!=EOF)
	{
		if (count<2)	/* Decimal Format (40.139722) */
			bearing=degrees;

		else		/* DMS Format (40 08 23.xx) */
		{
			bearing=fabs(degrees);
			bearing+=fabs(minutes/60.0);
			bearing+=fabs(seconds/3600.0);

			if ((degrees<0.0) || (minutes<0.0) || (seconds<0.0))
				bearing=-bearing;
		}

	}

	else
		bearing=0.0;

	return bearing;
}

char ReadQTH(void)
{
	char str[25];
	FILE *fd=NULL;

	fd=fopen(qthfile,"r");

	if (fd!=NULL)
	{
		fgets(qth.callsign,16,fd);
		qth.callsign[strlen(qth.callsign)-1]=0;

		fgets(str,20,fd);
		qth.stnlat=ReadBearing(str);

		fgets(str,20,fd);
		qth.stnlong=ReadBearing(str);

		fgets(str,20,fd);
		qth.stnalt=atoi(str);

		fclose(fd);

		obs_geodetic.lat=qth.stnlat*deg2rad;
		obs_geodetic.lon=-qth.stnlong*deg2rad;
		obs_geodetic.alt=((double)qth.stnalt)/1000.0;
		obs_geodetic.theta=0.0;
	}

	if (fd==NULL)
		return 0;
	else
		return 1;
}

char read_xponders(void)
{
	int x, z, entry=0, max_entries=10, transponders=0;
	char xponder_file[255], matches=0, name[80], line[80];
	unsigned long catnum;
	FILE *fd=NULL;

	for (x=0; x<24; x++)
	{
		/* Clear the need_to_save flag */

		sat_xponder[x].need_to_save=0;

		/* Create the transponder filename */

		snprintf(xponder_file,255,"%s/.predict/xponders/%u.xpn%c",getenv("HOME"),sat[x].catnum,0);

		fd=fopen(xponder_file,"r");

		if (fd!=NULL)
		{
			fgets(line,29,fd);

			while (strncasecmp(line,"end",3)!=0 && line[0]!='\n' && feof(fd)==0)
			{
				/* The first line is the satellite name. */

				strncpy2(name,line,29);

				/* The second is the Catalog (Object) Number */

				fgets(line,25,fd);
				sscanf(line,"%ld",&catnum);

				if (catnum==sat[x].catnum)
				{
					matches++;
					database=1;
					strncpy2(sat_xponder[x].name,sat[x].name,29);
					sat_xponder[x].catnum=sat[x].catnum;

					fgets(line,40,fd);  /* Alat, Alon */

					if (strncasecmp(line,"No",2)!=0)
					{
						sscanf(line,"%lf, %lf",&sat_xponder[x].alat, &sat_xponder[x].alon);
						sat_xponder[x].squintflag=1;
					}

					else
						sat_xponder[x].squintflag=0;

					fgets(line,75,fd); /* Transponder name */

					while (strncasecmp(line,"end",3)!=0 && line[0]!='\n' && feof(fd)==0)
					{
						if (entry<max_entries)
						{
							if (strncmp(line,"No",2)!=0)
							{
								line[strlen(line)-1]=0;
								strncpy2(sat_xponder[x].transponder_name[entry],line,49);
							}

							else
								sat_xponder[x].transponder_name[entry][0]=0;

							fgets(line,40,fd); /* Uplink passband */ 
							sscanf(line,"%lf, %lf", &sat_xponder[x].uplink_start[entry], &sat_xponder[x].uplink_end[entry]);

							fgets(line,40,fd); /* Downlink passband */
							sscanf(line,"%lf, %lf", &sat_xponder[x].downlink_start[entry], &sat_xponder[x].downlink_end[entry]);

							fgets(line,40,fd); /* Weekly schedule */

							if (strncasecmp(line,"No",2)!=0)
							{
								for (z=0; z<8; z++)
								{
									if (strncasecmp(line,day[z],3)==0)
									{
										sat_xponder[x].dayofweek[entry]=z;
										break;
									}
								}
							}

							else
								sat_xponder[x].dayofweek[entry]=0;

							fgets(line,40,fd);  /* Orbital schedule */

							if (strncasecmp(line,"No",2)!=0)
								sscanf(line,"%d, %d",&sat_xponder[x].phase_start[entry], &sat_xponder[x].phase_end[entry]);

							else
							{
								sat_xponder[x].phase_start[entry]=0;
								sat_xponder[x].phase_end[entry]=0;
							}

							if (sat_xponder[x].uplink_start[entry]!=0.0 || sat_xponder[x].downlink_start[entry]!=0.0)
								transponders++;

							entry++;
						}

						fgets(line,75,fd);  /* Get the next transponder name */

					} // while not end of transponders for this satellite

					fgets(line,75,fd);

					sat_xponder[x].transponders=transponders;

					entry=0;
					transponders=0;

				} // end if (we're finished with this satellite)

			} // end while (we're finished with this file)

			fclose(fd);

		}

		else /* (fd==NULL) */
		{
			/* Clear out the entry */

			sat_xponder[x].name[0]=0;
			sat_xponder[x].catnum=0;
			sat_xponder[x].squintflag=0;
			sat_xponder[x].alat=0.0;
			sat_xponder[x].alon=0.0;
			sat_xponder[x].transponders=0;

			for (z=0; z<10; z++)
			{
				sat_xponder[x].transponder_name[z][0]=0;
				sat_xponder[x].uplink_start[z]=0.0;
				sat_xponder[x].uplink_end[z]=0.0;
				sat_xponder[x].downlink_start[z]=0.0;
				sat_xponder[x].downlink_end[z]=0.0;
				sat_xponder[x].dayofweek[z]=0;
				sat_xponder[x].phase_start[z]=0;
				sat_xponder[x].phase_end[z]=0;
			}

			sat_xponder[x].need_to_save=0;
		}

	} // end for loop

	return matches;
}

uint32_t read_tle_file (char *filename)
{
	/* This function reads the "filename.tle" file
	   and populates the sat[x] data structure only
	   with MORE RECENT Keplerian orbital data read.
	   It returns a 32-bit value whose bit positions
	   flag which satellites have been updated. */ 

	int x=0, y;
	uint32_t updated=0;
	unsigned catnum, setnum, year, orbitnum;
	double tempnum, refepoch, db_epoch, tle_epoch, incl, raan, eccn, argper, meanan, meanmo, drag, nddot6, bstar;
	char found, satname[80], designator[15], name[80], line1[80], line2[80], text[255];

	FILE *infile=NULL;

	infile=fopen(filename,"r");

	if (infile!=NULL)
	{
		for (x=0; x<24 && feof(infile)==0; x++)
		{
			/* Initialize variables */

			name[0]=0;
			line1[0]=0;
			line2[0]=0;

			/* Read the 2-line element set */

			fgets(name,75,infile);
			fgets(line1,75,infile);
			fgets(line2,75,infile);

			if (tle_check(line1,line2) && (feof(infile)==0))
			{
				/* We found a valid TLE! */

				/* Some TLE sources left justify the sat
				   name in a 24-byte field that is padded
				   with blanks.  The following lines cut
				   out the blanks as well as the line feed
				   character read by the fgets() function. */
 
				y=strlen(name);

				while (name[y]==32 || name[y]==0 || name[y]==10 || name[y]==13 || y==0)
				{
					name[y]=0;
					y--;
				}

				if (y<=0)
					strncpy2(satname,"Empty",5);

				else
					strncpy2(satname,name,24);
		
				/* Read the TLE data */

				if (isdigit(line1[9])!=0 && isdigit(line1[10])!=0)
				{  
					year=10*(line1[9]-'0')+line1[10]-'0';

					if (year<57)
						year+=2000;
					else
						year+=1900;

					snprintf(designator,13,"%4u-%s%c",year,subString(line1,11,16),0);
				}

				else
				{
					designator[0]='0';
					designator[1]=0;
				}

				catnum=atol(subString(line1,2,6));
				year=2000+atoi(subString(line1,18,19));
				refepoch=atof(subString(line1,20,31));
				tempnum=1.0e-5*atof(subString(line1,44,49));
				nddot6=tempnum/pow(10.0,(line1[51]-'0'));
				tempnum=1.0e-5*atof(subString(line1,53,58));
				bstar=tempnum/pow(10.0,(line1[60]-'0'));
				setnum=atol(subString(line1,64,67));
				incl=atof(subString(line2,8,15));
				raan=atof(subString(line2,17,24));
				eccn=1.0e-07*atof(subString(line2,26,32));
				argper=atof(subString(line2,34,41));
				meanan=atof(subString(line2,43,50));
				meanmo=atof(subString(line2,52,62));
				drag=atof(subString(line1,33,42));
				orbitnum=atof(subString(line2,63,67));

				/* Look for a matching catalog number in
				   PREDICT's Keplerian orbital database. */

				for (y=0, found=0; y<24 && found==0; y++)
				{
					if (catnum==sat[y].catnum)
					{
						db_epoch=(365.25*sat[y].year)+sat[y].refepoch;
						tle_epoch=(365.25*year)+refepoch;

						if (tle_epoch>db_epoch)
						{
							snprintf(text,254,"%s\n%u\n%s\n%s\n%.7lf\n%.7lf\n%.7lf\n%.7lf\n%.7lf\n%.7lf\n%e\n%e\n%e\n%u\n%u\n",satname,catnum,designator,epoch2string(year,refepoch),incl,raan,eccn,argper,meanan, meanmo, drag,nddot6,bstar,orbitnum,setnum);
							strncpy2(sat[y].text,text,254);
							found=1;
							keptext2data(y);
							updated|=(1<<y);
						}
					}
				}

			}
		}

		fclose(infile);
	}

	return updated;
}

uint32_t read_json_file (char *filename)
{
	/* This function reads a "filename.json" file
	   and populates the sat[x] data structure with
	   MORE RECENT Keplerian orbital data read in the
	   JSON file.  It returns a 32-bit value whose bit
	   positions flag which satellites have been updated. */ 

	unsigned x, y;
	uint32_t updated=0;
	double json_epoch, db_epoch;
	char string[255], keyword[55], value[55], delimiter, end_of_block, str[15][30], found;

	FILE *fd=NULL;

	fd=fopen(filename,"r");

	if (fd!=NULL)
	{
		while (feof(fd)==0)  /* Loop until EOF is reached */
		{
			/* The beginning of each block begins with a "{" */

			fscanf(fd,"%250[^{]s", string);

			/* Read the delimiter (opening curly brace) */

			fscanf(fd,"%c",&delimiter);

			/* Initialize the str[] array string */

			for (y=0; y<15; y++)
				str[y][0]=0;

			end_of_block=0;

			while (end_of_block==0)
			{
				/* Each line of data is comma delimited except */
				/* the last in the block that ends with a "}," */

				fscanf(fd,"%250[^,]s", string);
				fscanf(fd,"%c",&delimiter);

				/* If the string contains a colon (:),
				   then we have data to process. */

				if (strstr(string,":")!=NULL)
				{
					/* Read the JSON keyword and value fields */

					strncpy2(keyword,getJSONkeyword(string),50);
					strncpy2(value,getJSONvalue(string),50);

					/* Grab the values we're interested in */

					if (strcmp("OBJECT_NAME",keyword)==0 && str[0][0]==0)
						strncpy2(str[0],value,25);

					else if (strcmp("AMSAT_NAME",keyword)==0)
						strncpy2(str[0],value,25);

					else if (strcmp("NORAD_CAT_ID",keyword)==0)
						strncpy2(str[1],value,14);

					else if (strcmp("OBJECT_ID",keyword)==0)
						strncpy2(str[2],value,14);

					else if (strcmp("EPOCH",keyword)==0)
						strncpy2(str[3],value,30);

					else if (strcmp("INCLINATION",keyword)==0)
						strncpy2(str[4],value,20);

					else if (strcmp("RA_OF_ASC_NODE",keyword)==0)
						strncpy2(str[5],value,20);

					else if (strcmp("ECCENTRICITY",keyword)==0)
						strncpy2(str[6],value,20);

					else if (strcmp("ARG_OF_PERICENTER",keyword)==0)
						strncpy2(str[7],value,20);

					else if (strcmp("MEAN_ANOMALY",keyword)==0)
						strncpy2(str[8],value,20);

					else if (strcmp("MEAN_MOTION",keyword)==0)
						strncpy2(str[9],value,20);

					else if (strcmp("MEAN_MOTION_DOT",keyword)==0)
						strncpy2(str[10],value,20); // Decay Rate

					else if (strcmp("MEAN_MOTION_DDOT",keyword)==0)
						strncpy2(str[11],value,20); // NDDOT 6

					else if (strcmp("BSTAR",keyword)==0)
						strncpy2(str[12],value,20);

					else if (strcmp("REV_AT_EPOCH",keyword)==0)
						strncpy2(str[13],value,20);

					else if (strcmp("ELEMENT_SET_NO",keyword)==0)
						strncpy2(str[14],value,20);
				}

				if (strstr(string,"}")!=NULL)
					end_of_block=1;
			}

			/* Look for a matching catalog number in
			   PREDICT's Keplerian orbital database. */

			/* NOT getting here if the file is not a JSON type. */

			for (x=0, found=0; x<24 && found==0; x++)
			{
				if ((unsigned)atoi(str[1])==sat[x].catnum)
				{
					str[3][10]='T'; /* Sometimes the 'T' is missing from the EPOCH string */
					strncpy2(value,str[3],30);
					value[4]=0; /* Truncate to the 4-digit year */

					json_epoch=(365.25*(unsigned)atoi(value))+string2epoch(str[3]);
					db_epoch=(365.25*sat[x].year)+sat[x].refepoch;

					if (json_epoch>=db_epoch)
					{
						snprintf(string,254,"%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",sat[x].name,str[1],str[2],str[3],str[4],str[5],str[6],str[7],str[8],str[9],str[10],str[11],str[12],str[13],str[14]);

						strncpy2(sat[x].text,string,254);

						found=1;

						/* Next, assign the satellite structure elements using the
						   data just stored in the satellite's text field string. */

						keptext2data(x);

						updated|=(1<<x);
					}
				}
			}
		}

		fclose(fd);
	}

	return updated;
}

uint32_t read_kvn_file (char *filename)
{
	/* This function reads a "filename.kvn" file
	   and populates the sat[x] data structure with
	   MORE RECENT Keplerian orbital data read in the
	   KVN file.  It returns a 32-bit value whose bit
	   positions flag which satellites have been updated. */

	unsigned x, found;
	uint32_t updated=0;
	double kvn_epoch, db_epoch;
	char line[80], str[15][30], value[55], string[255];

	FILE *fd=NULL;

	fd=fopen(filename,"r");

	if (fd!=NULL)
	{
		/* Initialize the str[] array */

		for (x=0; x<15; str[x][0]=0, x++);

		fgets(line,75,fd);

		do
		{
			if (strncmp("OBJECT_NAME",beforeEquals(line),11)==0 && feof(fd)==0)
				strncpy2(str[0],afterEquals(line),25);

			else if (strncmp("NORAD_CAT_ID",beforeEquals(line),12)==0 && feof(fd)==0)
				strncpy2(str[1],afterEquals(line),25);

			else if (strncmp("OBJECT_ID",beforeEquals(line),9)==0 && feof(fd)==0)
				strncpy2(str[2],afterEquals(line),25);

			else if (strncmp("EPOCH",beforeEquals(line),5)==0 && feof(fd)==0)
				strncpy2(str[3],afterEquals(line),25);

			else if (strncmp("INCLINATION",beforeEquals(line),10)==0 && feof(fd)==0)
				strncpy2(str[4],afterEquals(line),25);

			else if (strncmp("RA_OF_ASC_NODE",beforeEquals(line),14)==0 && feof(fd)==0)
				strncpy2(str[5],afterEquals(line),25);

			else if (strncmp("ECCENTRICITY",beforeEquals(line),11)==0 && feof(fd)==0)
				strncpy2(str[6],afterEquals(line),25);

			else if (strncmp("ARG_OF_PERICENTER",beforeEquals(line),17)==0 && feof(fd)==0)
				strncpy2(str[7],afterEquals(line),25);

			else if (strncmp("MEAN_ANOMALY",beforeEquals(line),12)==0 && feof(fd)==0)
				strncpy2(str[8],afterEquals(line),25);

			else if (strcmp("MEAN_MOTION",beforeEquals(line))==0 && feof(fd)==0)
				strncpy2(str[9],afterEquals(line),25);

			else if (strcmp("MEAN_MOTION_DOT",beforeEquals(line))==0 && feof(fd)==0)
				strncpy2(str[10],afterEquals(line),25);

			else if (strcmp("MEAN_MOTION_DDOT",beforeEquals(line))==0 && feof(fd)==0)
				strncpy2(str[11],afterEquals(line),25);

			else if (strncmp("BSTAR",beforeEquals(line),5)==0 && feof(fd)==0)
				strncpy2(str[12],afterEquals(line),25);

			else if (strncmp("REV_AT_EPOCH",beforeEquals(line),12)==0 && feof(fd)==0)
				strncpy2(str[13],afterEquals(line),25);

			else if (strncmp("ELEMENT_SET_NO",beforeEquals(line),14)==0 && feof(fd)==0)
				strncpy2(str[14],afterEquals(line),25);

			/* Has a complete element set has been read thus far? */

			for (x=0, found=0; x<15; x++)
			{
				if (str[x][0]!=0)
					found++;
			}

			if (found==15)
			{
				/* But is this a satellite we're interested in? */

				for (x=0, found=0; x<24 && found==0; x++)
				{
					if ((unsigned)atoi(str[1])==sat[x].catnum)
					{
						str[3][10]='T'; /* Sometimes the 'T' is missing from the EPOCH string */
						strncpy2(value,str[3],30);
						value[4]=0; /* Truncate to the 4-digit year */

						kvn_epoch=(365.25*(unsigned)atoi(value))+string2epoch(str[3]);
						db_epoch=(365.25*sat[x].year)+sat[x].refepoch;

						if (kvn_epoch>=db_epoch)
						{
							snprintf(string,254,"%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",sat[x].name,str[1],str[2],str[3],str[4],str[5],str[6],str[7],str[8],str[9],str[10],str[11],str[12],str[13],str[14]);

							strncpy2(sat[x].text,string,254);

							found=1;

							/* Next, assign the satellite structure elements using the
							   data just stored in the satellite's text field string. */

							keptext2data(x);

							updated|=(1<<x);

							/* Initialize the str[] array */

							for (x=0; x<15; str[x][0]=0, x++);
						}
					}
				}
			}

			fgets(line,75,fd);

		} while (feof(fd)==0);

		fclose(fd);
	}
		
	return updated;
}

uint32_t read_csv_file (char *filename)
{
	/* This function reads a "filename.csv" file
	   and populates the sat[x] data structure with
	   Keplerian orbital data MORE RECENT than what we
	   already have.  It returns a 32-bit value whose bit
	   positions flag which satellites have been updated. */

	uint32_t updated=0;
	char string[255], field_name[25][25], delimiter, str[15][30], value[55];
	unsigned x, map[20], field, total_fields, found;
	double csv_epoch, db_epoch;

	FILE *fd=NULL;

	fd=fopen(filename,"r");

	if (fd!=NULL)
	{
		/* Read and store the keywords (names / definitions) of   */
		/* each data field located on the first line of the file. */

		field=0;
		delimiter=0;

		while (delimiter!='\n' && delimiter!='\r' && field<54 && feof(fd)==0)
		{
			/* Delimiters are commas, newlines, and carriage returns */

			fscanf(fd,"%20[^\r\n,]s", field_name[field]);
			fscanf(fd,"%c",&delimiter);

			if (delimiter=='\r')
				fscanf(fd,"%c",&delimiter);  /* Grab the '\n' that follows */

			field++;
		}

		total_fields=field;

		/* Initialize the mapping table. */

		for (x=0; x<15; map[x]=0, x++);

		/* Map the keywords (definitions) to PREDICT's KEP ordering scheme. */

		for (x=0; x<total_fields; x++)
		{
			if (strncmp("OBJECT_NAME",field_name[x],11)==0)
				map[0]=x;

			else if (strncmp("NORAD_CAT_ID",field_name[x],12)==0)
				map[1]=x;

			else if (strncmp("OBJECT_ID",field_name[x],9)==0)
				map[2]=x;

			else if (strncmp("EPOCH",field_name[x],5)==0)
				map[3]=x;

			else if (strncmp("INCLINATION",field_name[x],10)==0)
				map[4]=x;

			else if (strncmp("RA_OF_ASC_NODE",field_name[x],14)==0)
				map[5]=x;

			else if (strncmp("ECCENTRICITY",field_name[x],11)==0)
				map[6]=x;

			else if (strncmp("ARG_OF_PERICENTER",field_name[x],17)==0)
				map[7]=x;

			else if (strncmp("MEAN_ANOMALY",field_name[x],12)==0)
				map[8]=x;

			else if (strcmp("MEAN_MOTION",field_name[x])==0)
				map[9]=x;

			else if (strncmp("MEAN_MOTION_DOT",field_name[x],15)==0)
				map[10]=x;

			else if (strncmp("MEAN_MOTION_DDOT",field_name[x],16)==0)
				map[11]=x;

			else if (strncmp("BSTAR",field_name[x],5)==0)
				map[12]=x;

			else if (strncmp("REV_AT_EPOCH",field_name[x],12)==0)
				map[13]=x;

			else if (strncmp("ELEMENT_SET_NO",field_name[x],14)==0)
				map[14]=x;
		}

		if (total_fields)
		{
			/* Parse all the field values for each data set (satellite) */

			do
			{
				/* Read a comma (or new-line) delimited line of data. */
				/* IOW, read all "fields" (or "columns") on the line. */

				for (field=0, delimiter=0; field<total_fields && feof(fd)==0 && delimiter!='\n' && delimiter!='\r'; field++)
				{
					fscanf(fd, "%50[^\r\n,]s", string);
					fscanf(fd, "%c", &delimiter);

					if (delimiter=='\r')
						fscanf(fd, "%c", &delimiter); /* Grab the '\n' that follows */

					if (delimiter==',' || delimiter=='\n' || delimiter=='\r')	
					{
						/* Is this a field of data we're interested in? */

						for (x=0, found=0; x<15 && found==0; x++)
						{
							if (field==map[x])
							{
								if (string[0]==0)
								{
									string[0]='0';
									string[1]=0;
								}

								strncpy2(str[x],string,24);

								found=1;
							}
						}
					}
				}

				/* The line of data is finished! */
				/* But is this a satellite we're interested in? */

				for (x=0, found=0; x<24 && found==0; x++)
				{
					if ((unsigned)atoi(str[1])==sat[x].catnum)
					{
						str[3][10]='T'; /* Sometimes the 'T' is missing from the EPOCH string */
						strncpy2(value,str[3],30);
						value[4]=0; /* Truncate to the 4-digit year */

						csv_epoch=(365.25*(unsigned)atoi(value))+string2epoch(str[3]);
						db_epoch=(365.25*sat[x].year)+sat[x].refepoch;

						if (csv_epoch>=db_epoch)
						{
							snprintf(string,254,"%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%c",sat[x].name,str[1],str[2],str[3],str[4],str[5],str[6],str[7],str[8],str[9],str[10],str[11],str[12],str[13],str[14],0);

							strncpy2(sat[x].text,string,254);

							found=1;

							/* Next, assign the satellite structure elements using the
							   data just stored in the satellite's text field string. */

							keptext2data(x);

							updated|=(1<<x);

							/* Initialize the str[] array */

							for (x=0; x<15; str[x][0]=0, x++);
						}
					}
				}

			} while (feof(fd)==0);
		}

		fclose (fd);
	}

	return updated;
}

uint32_t read_xml_file (char *filename)
{
	/* This function reads a "filename.xml" file
	   and populates the sat[x] data structure with
	   MORE RECENT Keplerian orbital data read in the
	   XML file.  It returns a 32-bit value whose bit
	   positions flag which satellites have been updated. */

	unsigned x;
	uint32_t updated=0;
	char string[255], ending[2], str[15][30], value[55], found, done;
	double xml_epoch, db_epoch;

	FILE *fd=NULL;

	fd=fopen(filename,"r");

	if (fd!=NULL)
	{
		done=0;

		while (feof(fd)==0)
		{
			fscanf(fd,"%250[^>]s",string);
			removeLeadingSpaces(string);

			fscanf(fd,"%c",&ending[0]);
			ending[1]=0;
			strcat(string,ending);

			if (strcmp("</segment>",string)==0)
				done=1;

			/* Grab the values we're interested in */

			else if (strcmp("<OBJECT_NAME>",string)==0 && str[0][0]==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[0],string,25);
			}

			else if (strcmp("<AMSAT_NAME>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[0],string,25);
			}

			else if (strcmp("<NORAD_CAT_ID>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[1],string,14);
			}

			else if (strcmp("<OBJECT_ID>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[2],string,14);	
			}

			else if (strcmp("<EPOCH>",string)==0)
			{
				fscanf(fd,"%30[^<]s",string);
				strncpy2(str[3],string,30);
			}

			else if (strcmp("<INCLINATION>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[4],string,20);
			}

			else if (strcmp("<RA_OF_ASC_NODE>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[5],string,20);
			}

			else if (strcmp("<ECCENTRICITY>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[6],string,20);
			}

			else if (strcmp("<ARG_OF_PERICENTER>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[7],string,20);
			}

			else if (strcmp("<MEAN_ANOMALY>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[8],string,20);
			}

			else if (strcmp("<MEAN_MOTION>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[9],string,20);
			}

			else if (strcmp("<MEAN_MOTION_DOT>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[10],string,20);  // Decay Rate
			}

			else if (strcmp("<MEAN_MOTION_DDOT>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[11],string,20); // NDDOT 6
			}

			else if (strcmp("<BSTAR>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[12],string,20);
			}

			else if (strcmp("<REV_AT_EPOCH>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[13],string,20);
			}

			else if (strcmp("<ELEMENT_SET_NO>",string)==0)
			{
				fscanf(fd,"%25[^<]s",string);
				strncpy2(str[14],string,20);
			}

			if (done)
			{
				/* Look for a matching catalog number in
				   PREDICT's Keplerian orbital database. */

				for (x=0, found=0; x<24 && found==0; x++)
				{
					if ((unsigned)atoi(str[1])==sat[x].catnum)
					{
						str[3][10]='T'; /* Sometimes the 'T' is missing from the EPOCH string */
						strncpy2(value,str[3],30);
						value[4]=0; /* Truncate to the 4-digit year */

						xml_epoch=(365.25*(unsigned)atoi(value))+string2epoch(str[3]);
						db_epoch=(365.25*sat[x].year)+sat[x].refepoch;

						if (xml_epoch>=db_epoch)
						{
							snprintf(string,254,"%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",sat[x].name,str[1],str[2],str[3],str[4],str[5],str[6],str[7],str[8],str[9],str[10],str[11],str[12],str[13],str[14]);
							strncpy2(sat[x].text,string,254);

							found=1;

							/* Next, assign the satellite structure elements using the
							   data just stored in the satellite's text field string. */

							keptext2data(x);

							updated|=(1<<x);
						}
					}

				}

				done=0;
			}
		}

		fclose(fd);
	}
	
	return updated;
}

char xponder_migration(void)
{
	/* This function looks for a legacy "~.predict/predict.db" file.
	   If no "~.predict/xponders directory exists, it creates the
	   directory and copies the predict.db transponder data into
	   separate files in the xponders directory, each named by object
	   number (NNNNN.xpn).  It is used when migrating from a legacy
	   PREDICT version 2.x.x installation to a version 3.x.x format.
	   It returns the number of satellites successfully converted to
	   the new format. */

	int error=0;
	char dirname[255], oldname[255], newname[255], string[255],
	     name[30], obj_num[10], converted=0;

	FILE *infile=NULL, *outfile=NULL;

	snprintf(dirname,255,"%s/.predict/xponders%c",getenv("HOME"),0);
	error=mkdir(dirname,0777);  /* If the dir already exists, set error flag */

	if (error==0)
	{
		snprintf(oldname,255,"%s/.predict/predict.db%c",getenv("HOME"),0);

		infile=fopen(oldname,"r");

		if (infile!=NULL)
		{
			fgets(name,29,infile);   /* Name */
			fgets(obj_num,9,infile); /* Object number */

			while (feof(infile)==0)
			{
				/* Remove the trailing '\n' */

				obj_num[strlen(obj_num)-1]=0;

				/* Generate the output path + filename */

				snprintf(newname,255,"%s/.predict/xponders/%s.xpn%c",getenv("HOME"),obj_num,0);

				/* Open the output file for writing */

				outfile=fopen(newname,"w");

				if (outfile!=NULL)
				{
					/* Write the satellite name and object number */

					fprintf(outfile,"%s%s\n",name,obj_num);

					/* Read and write the rest of the data until we reach the "end" marker */

					fgets(string,254,infile);

					do
					{
						fprintf(outfile,"%s",string);
						fgets(string,254,infile);

					} while (strncmp(string,"end",3)!=0 && feof(infile)==0);

					fprintf(outfile,"end\n");

					converted++;

					fclose(outfile);
				}

				/* Read data for the next satellite in predict.db */

				fgets(name,29,infile);   /* Name */
				fgets(obj_num,9,infile); /* Object number */
			}

			fclose(infile);
		}
	}

	return converted;
}

int LineEdit(int x, int y, char *input)
{
	/* An ncurses-based line editor loosly based on code published
	   by "Khaled.K" on Code Golf Stack Exchange on Apr 26, 2017. */

	char line[75]={0};
	static char mode='o';
	int i=0, j=0, k=0, z=0, c=0,
	    diff=0, button1=0, button3=0;

	/* i is the cursor position */
	/* z is the current size */

	/* Copy the input[] string to line[] */

	i=strlen(input);

	if (i>70)
		i=70;

	for (z=0; z<i && input[z]!=0; z++)
	{
		if (input[z]!=0)
			line[z]=input[z];
	}

	i=0;	/* Place edit position at the beginning of the line */

	do
	{
		move(y,x);

		/* Display the line to be edited */

		for (j=0; line[j]; j++)
		{
			if (j==i)
			{
				if (mode=='i')
					addch(A_REVERSE | line[j] | A_BLINK);
				else
					addch(A_REVERSE | line[j]);
			}
			else
				addch(line[j]);
		}

		if (j==i)
		{
			if (mode=='i')
				addch(A_REVERSE | ' ' | A_BLINK);
			else
				addch(A_REVERSE | ' ');
		}
		else
			addch(' ');

		printw("   ");

		/* Get the next character from the keyboard */

		c=getch();

		if (c==KEY_MOUSE)
		{
			getmouse(&event);

			button1=(event.bstate&BUTTON1_CLICKED || event.bstate&BUTTON1_DOUBLE_CLICKED || event.bstate&BUTTON1_PRESSED || event.bstate&BUTTON1_RELEASED);
			button3=(event.bstate&BUTTON3_CLICKED || event.bstate&BUTTON3_DOUBLE_CLICKED || event.bstate&BUTTON3_PRESSED || event.bstate&BUTTON3_RELEASED);

			if (button1 || button3)
				c=27;
		}

		switch(c)
		{
			case KEY_IC:  /* Insert Key */

			if (mode=='i')
				mode='o';

			else if (mode=='o')
				mode='i';

			break;

			case KEY_LEFT: /* Left Arrow.  Move cursor left. */

			if (i>0)
				i--;
			break;

			case KEY_RIGHT: /* Right Arrow.  Move cursor right. */

			if (i<z)
				i++;
			break;

			case KEY_HOME:	/* Home key.  Move cursor to the beginning of the line. */

			i=0;

			break;

			case KEY_END: /* End key.  Move cursor to the end of the line. */

			i=z;

			break;
			case 8: /* Backspace */
			case 127:
			case KEY_BACKSPACE:

			/* Remove the previous character. */

			if (i>0)
			{
				for (k=i-1; k<z; k++)
					line[k]=line[k+1];
				i--;
				z--;
			}

			break;

			case KEY_DC: /* Delete */

			/* Remove the next character. */

			if (i<z)
			{
				for (k=i; k<z; k++)
					line[k]=line[k+1];
				z--;
			}

			break;

			default: /* A printable character. */

			if (c>31 && c<127 && i<70 && z<70) 
			{
				if (mode=='i')
				{
					for (k=z; k>i; k--)
						line[k]=line[k-1];
				}

				line[i]=c;

				i++;
				z++;
			}
		}

	} while (c!='\n' && c!=27 && c!=9);

	if (c==27) /* ESC Key */
	{
		/* Abort any changes made to the line */

		diff=(strlen(input)-strlen(line));

		if (diff<0)
			diff=-diff;

		strncpy2(line,input,74);
	}

	/* Display the final result to overwrite the cursor block */

	mvprintw(y,x,"%s ",line);

	if (diff)
	{
		/* Clear the remainder of the line */

		for (z=0; z<diff; z++)
			addch(' ');
	}

	diff=strncmp(input,line,70);

	/* Copy the (possibly) edited line[] string back
	   to input[] so it can be returned by reference. */

        for (z=0; z<70 && line[z]!=0; z++)
        {
                if (line[z]!=0)
                        input[z]=line[z];
        }

        input[z]=0;

	if (diff)
		resave=1;

	return diff;
}

void SaveKEP (void)
{
	unsigned x;
	FILE *fd=NULL;

	fd=fopen(kepfile,"w");

	if (fd!=NULL)
	{
		for (x=0; x<24; x++)
			fprintf(fd,"%s",sat[x].text);

		fprintf(fd,"\n");

		fclose(fd);
	}
}

char *group_validator(char *name)
{
	/* This function validates the celestrak.org satellite group name given,
	   and returns a valid URL string for downloading the Keplerian Element Sets
	   for that group in CSV format.  It is used in the downloadKEP() function, */

	char *list[]={"stations","visual","weather","amateur","analyst",
		      "resource","sar","sarsat","dmc","tdrss","argos",
		      "geo","x-comm","science","gps-ops","glo-ops",
		      "galileo","science","geodetic","engineering",
		      "military","radar","cubesat","planet","spire",
		      "intelsat","ses","telsat","oneweb","quianfan",
		      "hulianwang","kuiper","iridium","orbcomm","globalstar",
		      "satnogs","other-comm","beidou","sbas","active","\0"};

	static char url[80]={0};

	int x=0;

	url[0]=0;

	while (url[0]==0 && list[x][0]!=0)
	{
		if (strcasecmp(list[x],name)==0)
			snprintf(url,79,"https://celestrak.org/NORAD/elements/gp.php?GROUP=%s&FORMAT=csv",list[x]);

		x++;
	}

	return url;
}

int autoUpdate (char *string)
{
	/* This function updates PREDICT's orbital datafile from a
	   file containing orbital data in any number of pre-defined
	   formats either through a menu (interactive mode) or via
	   the command line.  string=NULL in interactive mode.
	   Otherwise, string=filename of the datafile to be
	   used for updating purposes.  */

	char filename[255], interactive=0, savecount=0;

	int i, id=0;
	uint32_t success=0;

	filename[0]=0;

	if (string[0])
		strncpy2(filename,string,254);
	else
	{
		interactive=1;
		bkgdset(COLOR_PAIR(3));
		attrset(COLOR_PAIR(3)|A_BOLD);
		refresh();
		clear();

		for (i=5; i<8; i+=2)
			mvprintw(i,19,"------------------------------------------");

		mvprintw(6,19,"* Keplerian Database Auto Update Utility *");
		attrset(COLOR_PAIR(4)|A_BOLD);
		mvprintw(19,18,"Enter GP Keplerian Element Source File Name");
		attrset(COLOR_PAIR(9)|A_BOLD);
		mvprintw(21,10,"Must be a .csv, .xml, .tle, .kvn, or .json file type only!");
		attrset(COLOR_PAIR(2)|A_BOLD);
		mvprintw(13,18,"-=> ");
		LineEdit(22,13,filename);
	}

	/* Prevent "." and ".." from being used as a filename. */

	if (strlen(filename)==0 || strncmp(filename,".",1)==0 || strncmp(filename,"..",2)==0)
		return 0;

	id=file_id(filename);

	if (interactive && id==0)
	{
		bkgdset(COLOR_PAIR(5));
		attrset(COLOR_PAIR(5)|A_BOLD);
		clear();
		move(12,0);

		for (i=47; i>strlen(filename); i-=2)
			printw(" ");

		printw("*** ERROR: File \"%s\" not found! ***\n",filename);
		beep();
		attrset(COLOR_PAIR(7)|A_BOLD);
		AnyKey();
	}

	if (id==1)
		success=read_tle_file(filename);

	else if (id==2)
		success=read_csv_file(filename);

	else if (id==3)
		success=read_kvn_file(filename);

	else if (id==4)
		success=read_xml_file(filename);

	else if (id==5)
		success=read_json_file(filename);

	/* A successful non-interactive update */

	if (success)
		savecount=1;

	if (interactive)
	{
		clear();
		savecount=0;
		attrset(COLOR_PAIR(2)|A_BOLD);
		mvprintw(3,35,"Updated:\n\n\n\n\n");
		attrset(COLOR_PAIR(3)|A_BOLD);

		for (i=0; i<24; i++)
		{
			if (success & (1<<i))
			{
				printw("     %-15s",sat[i].name);
				savecount++;
			}
		}

		if (savecount==0)
			mvprintw(11,33,"<< Nothing >>");

		attrset(COLOR_PAIR(2)|A_BOLD);

		if (savecount==1)
			mvprintw(19,21,"  Only 1 satellite was updated.");
		else
		{
			if (savecount==24)
				mvprintw(19,21,"  All 24 satellites were updated!");
			else
				mvprintw(19,21,"%u out of 24 satellites were updated.",savecount);
		}
			
		refresh();

		if (strlen(filename) && id!=0) 
		{
			attrset(COLOR_PAIR(4)|A_BOLD);
			AnyKey();
		}
	}

	if (savecount)
		SaveKEP();

	return (savecount ? 0 : -1);
}

char downloadKEP (char quiet, char force)
{
	/* Update PREDICT's predict.kep file if it was last updated
	   more than 12 hours ago, or forced by setting the "force"
	   parameter to a non-zero value. */

	char	done=0, groups_list[100]={0}, csv_file[255]={0}, csv_home[255]={0},
		name[20]={0}, url[80]={0}, string[40]={0}, *pointer=NULL;

	unsigned long age=0L;

	FILE	*fd=NULL;

	age=file_age(kepfile);

	if (age>43200 || force!=0)
	{
		/* Open the ~/.predict/groups.list file for reading.  This file contains
		   the names of the satellite groups we wish to download to use in
		   updating PREDICT's "predict.kep" file.  If the groups.list file
		   doesn't exist, then don't download anything. */

		snprintf(groups_list,100,"%s/.predict/groups.list",getenv("HOME"));

		fd=fopen(groups_list,"r");

		if (fd!=NULL)
		{
			/* Download updated Keplerian Orbital Data from celestrak.org
			   and update PREDICT's orbital database file (predict.kep). */

			if (quiet==0)
			{
				/* Inform the human */

				fprintf(stdout,"Updating orbital data from celestrak.org...");
				fflush(stdout);
			}

			snprintf(csv_home,250,"%s/.predict",getenv("HOME"));

			fgets(string,39,fd);

			while (feof(fd)==0)
			{
				pointer=strchr(string,';');

				if (pointer!=NULL)
					*pointer=0;

				name[0]=0;

				sscanf(string,"%19s",name);

				strcpy(url,group_validator(name));

				if (url[0])
				{
					snprintf(csv_file,255,"%s/%s.csv",csv_home,name);	
					curl_download(url,csv_file);
					autoUpdate(csv_file);
				}

				fgets(string,39,fd);
			}

			fclose(fd);

			if (quiet==0)
			{
				/* Erase "Updating orbital data from celestrak.org..." */

				fprintf(stdout,"\033[2K");
				fflush(stdout);
			}
		}
		
		done=1;
	}

	return done;
}

char copyFile (char *destination, char *source)
{
	/* This function copies file "source" to file "destination"
	   in 64k chunks.  The permissions on the destination file
	   are set to rw-r--r--  (0644).  A 0 is returned if no
	   errors are encountered.  A 1 indicates a problem writing
	   to the destination file.  A 2 indicates a problem reading
	   the source file.  */

	int x, sd, dd;
	char error=0, buffer[65536];

	sd=open(source,O_RDONLY);

	if (sd!=-1)
	{
		dd=open(destination,O_WRONLY | O_CREAT| O_TRUNC, 0644);

		if (dd!=-1)
		{
			x=read(sd,&buffer,65536);

			while (x)
			{
				write(dd,&buffer,x);
				x=read(sd,&buffer,65536);
			}

			close(dd);
		}

		else
			error=1;

		close(sd);
	}

	else
		error+=2;

	return error;
}

char ReadKEP (void)
{
	/* Reads PREDICT's Keplerian Data File and updates the sat[]
	   data structure. There are 24 satellites and 15 lines of
	   data per satellite. */

	unsigned x, y;
	char value[15][30], success=-1;

	FILE *fd=NULL;

	fd=fopen(kepfile,"r");

	if (fd!=NULL)
	{
		for (x=0; x<24; x++)
		{
			for (y=0; y<15; y++)
				fgets(value[y],29,fd);

			snprintf(sat[x].text,255,"%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s",value[0],
			value[1], value[2], value[3], value[4], value[5], value[6], value[7],
			value[8], value[9], value[10], value[11], value[12], value[13], value[14]);

			keptext2data(x);
		}

		success=1;

		fclose(fd);
	}

	return success;
}

char tle_migration(void)
{
	/* This function reads the "~/predict.tle" file
	   from an older PREDICT version 2.x.x installation
	   and creates an equivalent "~/predict.kep" file
	   in the new PREDICT version 3.x.x format. Returns
	   the number of satellites successfully converted. */

	int x=0, y;
	unsigned catnum, setnum, year, orbitnum;
	double tempnum, refepoch, incl, raan, eccn, argper, meanan, meanmo, drag, nddot6, bstar;
	char converted=0, satname[80], designator[15], name[80], line1[80], line2[80], input[255], output[255];

	FILE *infile=NULL, *outfile=NULL;

	snprintf(input,255,"%s/.predict/predict.tle%c",getenv("HOME"),0);
	snprintf(output,255,"%s/.predict/predict.kep%c",getenv("HOME"),0);

	infile=fopen(input,"r");
	outfile=fopen(output,"w");

	if (infile!=NULL && outfile!=NULL)
	{
		for (x=0; x<24 && feof(infile)==0; x++)
		{
			/* Initialize variables */

			name[0]=0;
			line1[0]=0;
			line2[0]=0;

			/* Read element set */

			fgets(name,75,infile);
			fgets(line1,75,infile);
			fgets(line2,75,infile);

			if (tle_check(line1,line2) && (feof(infile)==0))
			{
				/* We found a valid TLE! */

				/* Some TLE sources left justify the sat
				   name in a 24-byte field that is padded
				   with blanks.  The following lines cut
				   out the blanks as well as the line feed
				   character read by the fgets() function. */
 
				y=strlen(name);

				while (name[y]==32 || name[y]==0 || name[y]==10 || name[y]==13 || y==0)
				{
					name[y]=0;
					y--;
				}

				if (y<=0)
					strncpy2(satname,"Empty",5);

				else
					strncpy2(satname,name,24);
		
				/* Read the TLE data */

				if (isdigit(line1[9])!=0 && isdigit(line1[10])!=0)
				{  
					year=10*(line1[9]-'0')+line1[10]-'0';

					if (year<57)
						year+=2000;
					else
						year+=1900;

					snprintf(designator,13,"%4u-%s%c",year,subString(line1,11,16),0);
				}

				else
				{
					designator[0]='0';
					designator[1]=0;
				}

				catnum=atol(subString(line1,2,6));
				year=2000+atoi(subString(line1,18,19));
				refepoch=atof(subString(line1,20,31));
				tempnum=1.0e-5*atof(subString(line1,44,49));
				nddot6=tempnum/pow(10.0,(line1[51]-'0'));
				tempnum=1.0e-5*atof(subString(line1,53,58));
				bstar=tempnum/pow(10.0,(line1[60]-'0'));
				setnum=atol(subString(line1,64,67));
				incl=atof(subString(line2,8,15));
				raan=atof(subString(line2,17,24));
				eccn=1.0e-07*atof(subString(line2,26,32));
				argper=atof(subString(line2,34,41));
				meanan=atof(subString(line2,43,50));
				meanmo=atof(subString(line2,52,62));
				drag=atof(subString(line1,33,42));
				orbitnum=atof(subString(line2,63,67));

				fprintf(outfile,"%s\n%u\n%s\n%s\n%.7lf\n%.7lf\n%.7lf\n%.7lf\n%.7lf\n%.7lf\n%e\n%e\n%e\n%u\n%u\n",satname,catnum,designator,epoch2string(year,refepoch),incl,raan,eccn,argper,meanan, meanmo, drag,nddot6,bstar,orbitnum,setnum);

				converted++;
			}
		}

		fclose(infile);
		fclose(outfile);

		/* Read the .kep file we just created */

		ReadKEP();
	}

	/* If there's no "groups.list" file, copy in a default
	   so that the Keplerian elements can be autoUpdated. */

	infile=NULL;
	snprintf(input,255,"%s/.predict/groups.list%c",getenv("HOME"),0);
	infile=fopen(input,"r");

	if (infile==NULL)
	{
		snprintf(output,255,"%s/.predict/groups.list",getenv("HOME"));
		snprintf(input,255,"%sdefault/groups.list",BUILD_DIR);
		copyFile(output,input);
	}

	else
		fclose(infile);

	/* Force a download (refresh) of the orbital data */

	downloadKEP(0,1);

	return converted;
}

char ReadDataFiles (void)
{
	/* This function reads "predict.qth", "predict.kep",
	   and (optionally) "predict.db" files into memory.
  	   Return values are as follows:

	   0: Neither the qth nor the kep files were loaded
	   1: Only the qth file was loaded
	   2: Only the kep file was loaded
	   3: The qth and kep files were loaded successfully */

	char result=0, qth_status, kep_status;

	qth_status=ReadQTH();
	kep_status=ReadKEP();
	read_xponders();

	if (qth_status!=1 && kep_status!=1)
		result=0;

	if (qth_status==1 && kep_status!=1)
		result=1;

	if (qth_status!=1 && kep_status==1)
		result=2;

	if (qth_status==1 && kep_status==1)
		result=3;

	return result;
}

int copyDir (char *destination, char *source)
{
	/* This function recursively copies a directory and it's contents
	   from "source" to "destination" using the system's "cp" command. */

	char error=0, command[255]={0};

	snprintf(command,255,"cp -r --preserve=timestamps %s %s\n",source, destination);

	error=system(command);

	return error;
}

void saveQTH (void)
{
	/* This function saves QTH data to the QTH data file. */

	FILE *fd=NULL;

	fd=fopen(qthfile,"w");

	if (fd!=NULL)
	{
		fprintf(fd,"%s\n",qth.callsign);
		fprintf(fd," %.8g\n",qth.stnlat);
		fprintf(fd," %.8g\n",qth.stnlong);
		fprintf(fd," %d\n",qth.stnalt);

		fclose(fd);
	}
}

void save_xponders(void)
{
	/* Save the transponder database files.
	   These are named by object number and
	   are located under $HOME/.predict/xponders */

	int x, y;
	char xponder_file[255];

	FILE *fd=NULL;

	for (x=0; x<24; x++)
	{
		if (sat_xponder[x].need_to_save)
		{
			/* Generate the filename */

			snprintf(xponder_file,255,"%s/.predict/xponders/%u.xpn%c",getenv("HOME"),sat[x].catnum,0);

			fd=fopen(xponder_file,"w");

			if (fd!=NULL)
			{
				if (sat_xponder[x].name[0]==0)
					fprintf(fd,"%s\n",sat[x].name);
				else
					fprintf(fd,"%s\n",sat_xponder[x].name);

				if (sat_xponder[x].catnum==0)
					fprintf(fd,"%u\n",sat[x].catnum);
				else
					fprintf(fd,"%u\n",sat_xponder[x].catnum);

				if (sat_xponder[x].squintflag)
					fprintf(fd,"%f, %f\n",sat_xponder[x].alat,sat_xponder[x].alon);
				else
					fprintf(fd,"No alat, alon\n");

				for (y=0; (y<sat_xponder[x].transponders && y<10); y++)
				{
					fprintf(fd,"%s\n",sat_xponder[x].transponder_name[y]);
					fprintf(fd,"%.4f, %.4f\n",sat_xponder[x].uplink_start[y],sat_xponder[x].uplink_end[y]);
					fprintf(fd,"%.4f, %.4f\n",sat_xponder[x].downlink_start[y],sat_xponder[x].downlink_end[y]);

					if (sat_xponder[x].dayofweek[y]==0)
						fprintf(fd,"No weekly schedule\n");
					else
						fprintf(fd,"%s\n",day[sat_xponder[x].dayofweek[y]]);

					if (sat_xponder[x].phase_start[y]==0 && sat_xponder[x].phase_end[y]==0)
						fprintf(fd,"No orbital schedule\n");
					else
						fprintf(fd,"%d, %d\n",sat_xponder[x].phase_start[y],sat_xponder[x].phase_end[y]);
				}

				fprintf(fd,"end\n");

				sat_xponder[x].need_to_save=0;

				fclose(fd);
			}

		}
	}
}

int Select (void)
{
	/* This function displays the names of satellites contained
	   within the program's database and returns an index that
	   corresponds to the satellite selected by the user.  An
	   ESC, CR, or Right mouse click returns a -1. */

	int x, y, yy, z, ch, key=0, button1=0, button3=0;

	clear();

	bkgdset(COLOR_PAIR(2)|A_BOLD);
	printw("\n\n\t\t\t      Select a Satellite:\n\n");

	attroff(COLOR_PAIR(2)|A_BOLD);

	attrset(COLOR_PAIR(3)|A_BOLD);

	for (x=0, y=8, z=16; y<16; ++x, ++y, ++z)
	{
		printw("\n\t[%c]: %-15s", x+'A', Abbreviate(sat[x].name,15));
		printw("\t[%c]: %-15s", y+'A', Abbreviate(sat[y].name,15));
		printw("\t[%c]: %-15s\n", z+'A', Abbreviate(sat[z].name,15));
	}

	attrset(COLOR_PAIR(4)|A_BOLD);

	printw("\n\n\t\t<< Enter Selection  -  Press [ESC] To Exit >>");
	refresh();

	do
	{
		ch=getch();
	
		if (ch==KEY_MOUSE)
		{
			getmouse(&event);

			button1=(event.bstate&BUTTON1_CLICKED || event.bstate&BUTTON1_DOUBLE_CLICKED || event.bstate&BUTTON1_PRESSED || event.bstate&BUTTON1_RELEASED);
			button3=(event.bstate&BUTTON3_CLICKED || event.bstate&BUTTON3_DOUBLE_CLICKED || event.bstate&BUTTON3_PRESSED || event.bstate&BUTTON3_RELEASED);

			key=-2;

			if (button1)  /* Left click */
			{
				/* In case someone clicks on [ESC] at the bottom */

				if (event.y==22 && event.x>=45 && event.x<=49)
					key=-1;

				for (y=5, yy=0; y<=19; y+=2)
				{
					if (event.y==y)
					{
						yy=y;
						break;
					}
				}

				if (yy)
				{
					if (event.x>=8 && event.x<=28)
						key=((yy/2)-2);

					if (event.x>=32 && event.x<=52)
						key=((yy/2)-2)+8;

					if (event.x>=56 && event.x<=76)
						key=((yy/2)-2)+16;
				}
			}
		}

		else  /* Keyboard entry */
		{
			if (ch==27)
				key=-1;
			else
				key=toupper(ch)-'A';
		}

		if (button3)    /* Right click */
			key=-1; /* Bail out! */

	} while (key<-1 || key>23);

	return (key);
}

long DayNum (int m, int d, int yyyy)
{
	/* This function calculates the Unix Day Number
	   where January 1, 1970 (Unix Epoch) = Day 0. */

	long dn;
	
	dn=doy(m,d,yyyy)+floor((yyyy-1970)*mean_solar_year);

	return dn;

}

double CurrentDaynum(void)
{
	/* Reads the system clock and returns the number
	   of days since 01-Jan-1970 00:00:00 UTC (daynum 0) */

	struct timeval tptr;
	double usecs, seconds;

	(void)gettimeofday(&tptr,NULL);
	usecs=0.000001*(double)tptr.tv_usec;
	seconds=usecs+(double)tptr.tv_sec;

	return (seconds/86400.0);
}

char *Daynum2String (double daynum)
{
	/* This function takes the given epoch as a fractional number of
	   days since 01-Jan-1970 00:00:00 UTC and returns the corresponding
	   date as a string of the form "Tue 12Oct99 17:22:37". */

	static char timestr[26];
	time_t t;
	struct tm *timeptr;

	t=(time_t)(86400.0*daynum);
	timeptr=gmtime(&t);
	strftime(timestr,sizeof(timestr),"%a %d%b%y %T", timeptr);

	return timestr;
}

double GetStartTime (int mode)
{
	/* This function prompts the user for the time and date
	   the user wishes to begin prediction calculations,
	   and returns the corresponding fractional day number.
	   01-Jan-1970 00:00:00 returns 0.  Default is NOW. */

	int	x, hr, min, sec ,mm=0, dd=0, yy; 
	char	good, mon[5], line[30], string[70], bozo_count=0,
		*month[12]={"Jan", "Feb", "Mar", "Apr", "May",
		"Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};

	do
	{
		bkgdset(COLOR_PAIR(2)|A_BOLD);
		clear();
		attroff(COLOR_PAIR(2)|A_BOLD);

		if (mode=='m')
			printw("\n\n\n\t     Starting UTC Date and Time for Predictions of the Moon\n\n");

		if (mode=='o')
			printw("\n\n\n\t     Starting UTC Date and Time for Predictions of the Sun\n\n");

		if (mode!='m' && mode!='o')
			printw("\n\n\n\t     Starting UTC Date and Time for Predictions of %-15s\n\n",sat[indx].name);

		bozo_count++;

		strcpy(string,Daynum2String(CurrentDaynum()));

		for (x=4; x<24; x++)
			string[x-4]=string[x];

		attrset(COLOR_PAIR(4)|A_BOLD);
		printw("\t\t    Format: %s -or- ",string);
		string[7]=0;
		printw("%s",string);
		attrset(COLOR_PAIR(4)|A_BOLD);
		mvprintw(21,30,"Default is `NOW'");
		attrset(COLOR_PAIR(3)|A_BOLD);
		mvprintw(13,1,"Enter Start Date & Time >> ");
		string[0]=0;
		refresh();
		LineEdit(28,13,string);

		if ((strncasecmp(string,"Now",3)==0) || (strlen(string)==0))
			return CurrentDaynum();

		strcpy(line,string);

		if (strlen(line)==7)
		{
			line[7]=' ';
			line[8]='0';
			line[9]='0';
			line[10]=':';
			line[11]='0';
			line[12]='0';
			line[13]=':';
			line[14]='0';
			line[15]='0';
			line[16]=0;
		}
			
		/* Check Day */
		good = (isdigit(line[0]) && isdigit(line[1])) ? 1 : 0;

		/* Month */
		good = (good && isalpha(line[2]) && isalpha(line[3]) && isalpha(line[4])) ? 1 : 0;

		/* Year */
		good = (good && isdigit(line[5]) && isdigit(line[6]) && (line[7]==' ')) ? 1 : 0;

		/* Hour */
		good = (good && isdigit(line[8]) && isdigit(line[9]) && (line[10]==':')) ? 1 : 0;

		/* Minute */
		good = (good && isdigit(line[11]) && isdigit(line[12]) && (line[13]==':')) ? 1 : 0;

		/* Seconds */
		good = (good && isdigit(line[14]) && isdigit(line[15])) ? 1 : 0;

	     	if (good)
		{ 
			/* Decode Day */
			dd=10*(line[0]-'0')+line[1]-'0';

			/* Decode Month Number */
			line[2]=toupper(line[2]);
			line[3]=tolower(line[3]);
			line[4]=tolower(line[4]);

			mon[0]=line[2];
			mon[1]=line[3];
			mon[2]=line[4];
			mon[3]=0;

			for (mm=0; (mm<12 && strcmp(mon,month[mm])!=0); mm++);

			mm++;
	
			good=(mm>12) ? 0 : 1;
		}

		if (good==0)
			beep();

	} while (good==0 && bozo_count<6);

	if (good==0)
	{
		/* If the user can't enter the starting date/time
		   correctly after several attempts, then the user
		   is a "bozo" and obviously can't follow directions. */

		bailout("Too Many Errors");
		exit(-1);
	}

	/* Decode Year */

	yy=2000+(10*(line[5]-'0')+line[6]-'0');

	/* Decode Time */

	for (x=8; x<16; x++)
		string[x-8]=line[x];

	string[8]=0;

	hr=10*(line[8]-'0')+line[9]-'0';
	min=10*(line[11]-'0')+line[12]-'0';
	sec=10*(line[14]-'0')+line[15]-'0';
	
	return ((double)DayNum(mm,dd,yy)+((hr/24.0)+(min/1440.0)+(sec/86400.0)));
}

void FindMoon (double daynum)
{
	/* This function determines the position of the moon, including
	   the azimuth and elevation headings, relative to the latitude
	   and longitude of the tracking station.  This code was derived
	   from a Javascript implementation of the Meeus method for
	   determining the exact position of the Moon found at:
	   http://www.geocities.com/s_perona/ingles/poslun.htm. */

	double	jd, ss, t, t1, t2, t3, d, ff, l1, m, m1, ex, om, l,
		b, w1, w2, bt, p, lm, h, ra, dec, z, ob, n, el, az,
		teg, th, mm, dv;

	jd=daynum+2440587.5; /* 1970-01-01 00:00:00.000 UTC */

	t=(jd-2415020.0)/36525.0;
	t2=t*t;
	t3=t2*t;
	l1=270.434164+481267.8831*t-0.001133*t2+0.0000019*t3;
	m=358.475833+35999.0498*t-0.00015*t2-0.0000033*t3;
	m1=296.104608+477198.8491*t+0.009192*t2+0.0000144*t3;
	d=350.737486+445267.1142*t-0.001436*t2+0.0000019*t3;
	ff=11.250889+483202.0251*t-0.003211*t2-0.0000003*t3;
	om=259.183275-1934.142*t+0.002078*t2+0.0000022*t3;
	om=om*deg2rad;
	
	/* Additive terms */

	l1=l1+0.000233*sin((51.2+20.2*t)*deg2rad);
	ss=0.003964*sin((346.56+132.87*t-0.0091731*t2)*deg2rad);
	l1=l1+ss+0.001964*sin(om);
	m=m-0.001778*sin((51.2+20.2*t)*deg2rad);
	m1=m1+0.000817*sin((51.2+20.2*t)*deg2rad);
	m1=m1+ss+0.002541*sin(om);
	d=d+0.002011*sin((51.2+20.2*t)*deg2rad);
	d=d+ss+0.001964*sin(om);
	ff=ff+ss-0.024691*sin(om);
	ff=ff-0.004328*sin(om+(275.05-2.3*t)*deg2rad);
	ex=1.0-0.002495*t-0.00000752*t2;
	om=om*deg2rad;

	l1=PrimeAngle(l1);
	m=PrimeAngle(m);
	m1=PrimeAngle(m1);
	d=PrimeAngle(d);
	ff=PrimeAngle(ff);
	om=PrimeAngle(om);

	m=m*deg2rad;
	m1=m1*deg2rad;
	d=d*deg2rad;
	ff=ff*deg2rad;

	/* Ecliptic Longitude */

	l=l1+6.28875*sin(m1)+1.274018*sin(2.0*d-m1)+0.658309*sin(2.0*d);
	l=l+0.213616*sin(2.0*m1)-ex*0.185596*sin(m)-0.114336*sin(2.0*ff);
	l=l+0.058793*sin(2.0*d-2.0*m1)+ex*0.057212*sin(2.0*d-m-m1)+0.05332*sin(2.0*d+m1);
	l=l+ex*0.045874*sin(2.0*d-m)+ex*0.041024*sin(m1-m)-0.034718*sin(d);
	l=l-ex*0.030465*sin(m+m1)+0.015326*sin(2.0*d-2.0*ff)-0.012528*sin(2.0*ff+m1);
	
	l=l-0.01098*sin(2.0*ff-m1)+0.010674*sin(4.0*d-m1)+0.010034*sin(3.0*m1);
	l=l+0.008548*sin(4.0*d-2.0*m1)-ex*0.00791*sin(m-m1+2.0*d)-ex*0.006783*sin(2.0*d+m);
	
	l=l+0.005162*sin(m1-d)+ex*0.005*sin(m+d)+ex*0.004049*sin(m1-m+2.0*d);
	l=l+0.003996*sin(2.0*m1+2.0*d)+0.003862*sin(4.0*d)+0.003665*sin(2.0*d-3.0*m1);

	l=l+ex*0.002695*sin(2.0*m1-m)+0.002602*sin(m1-2.0*ff-2.0*d)+ex*0.002396*sin(2.0*d-m-2.0*m1);

	l=l-0.002349*sin(m1+d)+ex*ex*0.002249*sin(2.0*d-2.0*m)-ex*0.002125*sin(2.0*m1+m);

	l=l-ex*ex*0.002079*sin(2.0*m)+ex*ex*0.002059*sin(2.0*d-m1-2.0*m)-0.001773*sin(m1+2.0*d-2.0*ff);

	l=l+ex*0.00122*sin(4.0*d-m-m1)-0.00111*sin(2.0*m1+2.0*ff)+0.000892*sin(m1-3.0*d);

	l=l-ex*0.000811*sin(m+m1+2.0*d)+ex*0.000761*sin(4.0*d-m-2.0*m1)+ex*ex*.000717*sin(m1-2.0*m);

	l=l+ex*ex*0.000704*sin(m1-2.0*m-2.0*d)+ex*0.000693*sin(m-2.0*m1+2.0*d)+ex*0.000598*sin(2.0*d-m-2.0*ff)+0.00055*sin(m1+4.0*d);

	l=l+0.000538*sin(4.0*m1)+ex*0.000521*sin(4.0*d-m)+0.000486*sin(2.0*m1-d);

	l=l-0.001595*sin(2.0*ff+2.0*d);

	/* Ecliptic latitude */

	b=5.128189*sin(ff)+0.280606*sin(m1+ff)+0.277693*sin(m1-ff)+0.173238*sin(2.0*d-ff);
	b=b+0.055413*sin(2.0*d+ff-m1)+0.046272*sin(2.0*d-ff-m1)+0.032573*sin(2.0*d+ff);

	b=b+0.017198*sin(2.0*m1+ff)+9.266999e-03*sin(2.0*d+m1-ff)+0.008823*sin(2.0*m1-ff);
	b=b+ex*0.008247*sin(2.0*d-m-ff)+0.004323*sin(2.0*d-ff-2.0*m1)+0.0042*sin(2.0*d+ff+m1);

	b=b+ex*0.003372*sin(ff-m-2.0*d)+ex*0.002472*sin(2.0*d+ff-m-m1)+ex*0.002222*sin(2.0*d+ff-m);

	b=b+0.002072*sin(2.0*d-ff-m-m1)+ex*0.001877*sin(ff-m+m1)+0.001828*sin(4.0*d-ff-m1);

	b=b-ex*0.001803*sin(ff+m)-0.00175*sin(3.0*ff)+ex*0.00157*sin(m1-m-ff)-0.001487*sin(ff+d)-ex*0.001481*sin(ff+m+m1)+ex*0.001417*sin(ff-m-m1)+ex*0.00135*sin(ff-m)+0.00133*sin(ff-d);

	b=b+0.001106*sin(ff+3.0*m1)+0.00102*sin(4.0*d-ff)+0.000833*sin(ff+4.0*d-m1);

	b=b+0.000781*sin(m1-3.0*ff)+0.00067*sin(ff+4.0*d-2.0*m1)+0.000606*sin(2.0*d-3.0*ff);

	b=b+0.000597*sin(2.0*d+2.0*m1-ff)+ex*0.000492*sin(2.0*d+m1-m-ff)+0.00045*sin(2.0*m1-ff-2.0*d);

	b=b+0.000439*sin(3.0*m1-ff)+0.000423*sin(ff+2.0*d+2.0*m1)+0.000422*sin(2.0*d-ff-3.0*m1);

	b=b-ex*0.000367*sin(m+ff+2.0*d-m1)-ex*0.000353*sin(m+ff+2.0*d)+0.000331*sin(ff+4.0*d);

	b=b+ex*0.000317*sin(2.0*d+ff-m+m1)+ex*ex*0.000306*sin(2.0*d-2.0*m-ff)-0.000283*sin(m1+3.0*ff);

	w1=0.0004664*cos(om*deg2rad);
	w2=0.0000754*cos((om+275.05-2.3*t)*deg2rad);
	bt=b*(1.0-w1-w2);

	/* Parallax calculations */

	p=0.950724+0.051818*cos(m1)+0.009531*cos(2.0*d-m1)+0.007843*cos(2.0*d)+0.002824*cos(2.0*m1)+0.000857*cos(2.0*d+m1)+ex*0.000533*cos(2.0*d-m)+ex*0.000401*cos(2.0*d-m-m1);

	p=p+0.000173*cos(3.0*m1)+0.000167*cos(4.0*d-m1)-ex*0.000111*cos(m)+0.000103*cos(4.0*d-2.0*m1)-0.000084*cos(2.0*m1-2.0*d)-ex*0.000083*cos(2.0*d+m)+0.000079*cos(2.0*d+2.0*m1);

	p=p+0.000072*cos(4.0*d)+ex*0.000064*cos(2.0*d-m+m1)-ex*0.000063*cos(2.0*d+m-m1);

	p=p+ex*0.000041*cos(m+d)+ex*0.000035*cos(2.0*m1-m)-0.000033*cos(3.0*m1-2.0*d);

	p=p-0.00003*cos(m1+d)-0.000029*cos(2.0*ff-2.0*d)-ex*0.000029*cos(2.0*m1+m);

	p=p+ex*ex*0.000026*cos(2.0*d-2.0*m)-0.000023*cos(2.0*ff-2.0*d+m1)+ex*0.000019*cos(4.0*d-m-m1);

	b=bt*deg2rad;
	lm=l*deg2rad;
	moon_dx=3.0/(pi*p);

	/* Semi-diameter calculation */
	/* sem=10800.0*asin(0.272488*p*deg2rad)/pi; */

	/* Convert ecliptic coordinates to equatorial coordinates */

	z=(jd-2415020.5)/365.2422;
	ob=23.452294-(0.46845*z+5.9e-07*z*z)/3600.0;
	ob=ob*deg2rad;
	dec=asin(sin(b)*cos(ob)+cos(b)*sin(ob)*sin(lm));
	ra=acos(cos(b)*cos(lm)/cos(dec));
	
	if (lm>pi)
		ra=twopi-ra;

	/* ra = right ascension */
	/* dec = declination */

	n=qth.stnlat*deg2rad;    /* North latitude of tracking station */

	/* Find siderial time in radians */

	t=(jd-2451545.0)/36525.0;
	teg=280.46061837+360.98564736629*(jd-2451545.0)+(0.000387933*t-t*t/38710000.0)*t;

	while (teg>360.0)
		teg-=360.0;

	th=FixAngle((teg-qth.stnlong)*deg2rad);
	h=th-ra;

	az=atan2(sin(h),cos(h)*sin(n)-tan(dec)*cos(n))+pi;
	el=asin(sin(n)*sin(dec)+cos(n)*cos(dec)*cos(h));

	moon_az=az/deg2rad;
	moon_el=el/deg2rad;

	/* Radial velocity approximation.  This code was derived
	   from "Amateur Radio Software", by John Morris, GM4ANB,
	   published by the RSGB in 1985. */

	mm=FixAngle(1.319238+daynum*0.228027135);  /* mean moon position */
	t2=0.10976;
	t1=mm+t2*sin(mm);
	dv=0.01255*moon_dx*moon_dx*sin(t1)*(1.0+t2*cos(mm));
	dv=dv*4449.0;
	t1=6378.0;
	t2=384401.0;
	t3=t1*t2*(cos(dec)*cos(n)*sin(h));
	t3=t3/sqrt(t2*t2-t2*t1*sin(el));
	moon_dv=dv+t3*0.0753125;

	moon_dec=dec/deg2rad;
	moon_ra=ra/deg2rad;
	moon_gha=teg-moon_ra;

	if (moon_gha<0.0)
		moon_gha+=360.0;
}

void FindSun (double daynum)
{
	/* This function finds the position of the Sun */

	/* Zero vector for initializations */
	vector_t zero_vector={0,0,0,0};

	/* Solar ECI position vector  */
	vector_t solar_vector=zero_vector;

	/* Solar observed azi and ele vector  */
	vector_t solar_set=zero_vector;

	/* Solar right ascension and declination vector */
	vector_t solar_rad=zero_vector;

	/* Solar lat, long, alt vector */
	geodetic_t solar_latlonalt;

	// jul_utc=daynum+2444238.5;
	jul_utc=daynum+2440587.5; /* 1970-01-01 00:00:00.000 UTC */

	Calculate_Solar_Position(jul_utc, &solar_vector);
	Calculate_Obs(jul_utc, &solar_vector, &zero_vector, &obs_geodetic, &solar_set);
	sun_azi=Degrees(solar_set.x); 
	sun_ele=Degrees(solar_set.y);
	sun_range=1.0+((solar_set.z-AU)/AU);
	sun_range_rate=1000.0*solar_set.w;

	Calculate_LatLonAlt(jul_utc, &solar_vector, &solar_latlonalt);

	sun_lat=Degrees(solar_latlonalt.lat);
	sun_lon=360.0-Degrees(solar_latlonalt.lon);

	Calculate_RADec(jul_utc, &solar_vector, &zero_vector, &obs_geodetic, &solar_rad);

	sun_ra=Degrees(solar_rad.x);
	sun_dec=Degrees(solar_rad.y);
}

void PreCalc (int x)
{
	/* This function copies TLE data from PREDICT's sat structure
	   to the SGP4/SDP4's single dimensioned tle structure, and
	   prepares the tracking code for the update. */

	strcpy(tle.sat_name,sat[x].name);
	strcpy(tle.idesg,sat[x].designator);
	tle.catnr=sat[x].catnum;
	tle.day=sat[x].refepoch;
	tle.year=(double)sat[x].year;
	tle.xndt2o=sat[x].drag;
	tle.xndd6o=sat[x].nddot6;
	tle.bstar=sat[x].bstar;
	tle.xincl=sat[x].incl;
	tle.xnodeo=sat[x].raan;
	tle.eo=sat[x].eccn;
	tle.omegao=sat[x].argper;
	tle.xmo=sat[x].meanan;
	tle.xno=sat[x].meanmo;
	tle.revnum=sat[x].orbitnum;

	if (sat_xponder[x].squintflag)
	{
		calc_squint=1;
		alat=deg2rad*sat_xponder[x].alat;
		alon=deg2rad*sat_xponder[x].alon;
	}

	else
		calc_squint=0;
 
	/* Clear all flags */

	ClearFlag(ALL_FLAGS);

	/* Select ephemeris type.  This function will set or clear the
	   DEEP_SPACE_EPHEM_FLAG depending on the TLE parameters of the
	   satellite.  It will also pre-process tle members for the
	   ephemeris functions SGP4 or SDP4, so this function must
	   be called each time a new tle set is used. */

	select_ephemeris(&tle);
}

void Calc (void)
{
	/* This is the stuff we need to do repetitively while tracking. */

	/* Zero vector for initializations */
	vector_t zero_vector={0,0,0,0};

	/* Satellite position and velocity vectors */
	vector_t vel=zero_vector;
	vector_t pos=zero_vector;

	/* Satellite Az, El, Range, Range rate */
	vector_t obs_set;

	/* Solar ECI position vector  */
	vector_t solar_vector=zero_vector;

	/* Solar observed azi and ele vector  */
	vector_t solar_set;

	/* Satellite's predicted geodetic position */
	geodetic_t sat_geodetic;

	// jul_utc=daynum+2444238.5;
	jul_utc=daynum+2440587.5; /* 1970-01-01 00:00:00.000 UTC */

	/* Convert satellite's epoch time to Julian  */
	/* and calculate time since epoch in minutes */

	jul_epoch=Julian_Date_of_Epoch(tle.day, tle.year);

	tsince=(jul_utc-jul_epoch)*xmnpda;
	age=jul_utc-jul_epoch;

	/* Copy the ephemeris type in use to ephem string. */

		if (isFlagSet(DEEP_SPACE_EPHEM_FLAG))
			strcpy(ephem,"SDP4");
		else
			strcpy(ephem,"SGP4");

	/* Call NORAD routines according to deep-space flag. */

	if (isFlagSet(DEEP_SPACE_EPHEM_FLAG))
		SDP4(tsince, &tle, &pos, &vel);
	else
		SGP4(tsince, &tle, &pos, &vel);

	/* Scale position and velocity vectors to km and km/sec */

	Convert_Sat_State(&pos, &vel);

	/* Calculate velocity of satellite */

	Magnitude(&vel);
	sat_vel=vel.w;

	/** All angles in rads. Distance in km. Velocity in km/s **/
	/* Calculate satellite Azi, Ele, Range and Range-rate */

	Calculate_Obs(jul_utc, &pos, &vel, &obs_geodetic, &obs_set);

	/* Calculate satellite Lat North, Lon East and Alt. */

	Calculate_LatLonAlt(jul_utc, &pos, &sat_geodetic);

	/* Calculate squint angle */

	if (calc_squint)
		squint=(acos(-(ax*rx+ay*ry+az*rz)/obs_set.z))/deg2rad;

	/* Calculate solar position and satellite eclipse depth. */
	/* Also set or clear the satellite eclipsed flag accordingly. */

	Calculate_Solar_Position(jul_utc, &solar_vector);
	Calculate_Obs(jul_utc, &solar_vector, &zero_vector, &obs_geodetic, &solar_set);

	if (Sat_Eclipsed(&pos, &solar_vector, &eclipse_depth))
		SetFlag(SAT_ECLIPSED_FLAG);
	else
		ClearFlag(SAT_ECLIPSED_FLAG);

	if (isFlagSet(SAT_ECLIPSED_FLAG))
		sat_sun_status=0;  /* Eclipse */
	else
		sat_sun_status=1; /* In sunlight */

	/* Convert satellite and solar data */
	sat_azi=Degrees(obs_set.x);
	sat_ele=Degrees(obs_set.y);
	sat_range=obs_set.z;
	sat_range_rate=obs_set.w;
	sat_lat=Degrees(sat_geodetic.lat);
	sat_lon=Degrees(sat_geodetic.lon);
	sat_alt=sat_geodetic.alt;

	fk=12756.33*acos(xkmper/(xkmper+sat_alt));
	fm=fk/1.609344;

	rv=(long)floor((tle.xno*xmnpda/twopi+age*tle.bstar*ae)*age+tle.xmo/twopi)+tle.revnum;

	sun_azi=Degrees(solar_set.x); 
	sun_ele=Degrees(solar_set.y);

	irk=(long)rint(sat_range);
	isplat=(int)rint(sat_lat);
	isplong=(int)rint(360.0-sat_lon);
	iaz=(int)rint(sat_azi);
	iel=(int)rint(sat_ele);
	ma256=(int)rint(256.0*(phase/twopi));

	if (sat_sun_status)
	{
		findsun='*';

		if (rint(sat_ele)>=0.0)
		{
			if ((sun_ele<=-12.0) || (sun_ele<=ISS_EL_LIMIT && sat[indx].catnum==25544))
				findsun='+';
		}
	}
	else
		findsun=' ';
}

char AosHappens (int x)
{
	/* This function returns a 1 if the satellite pointed to by
	   "x" can ever rise above the horizon of the ground station. */

	double lin, sma, apogee;

	if (sat[x].meanmo==0.0)
		return 0;
	else
	{
		lin=sat[x].incl;

		if (lin>=90.0)
			lin=180.0-lin;

		sma=331.25*exp(log(1440.0/sat[x].meanmo)*(2.0/3.0));
		apogee=sma*(1.0+sat[x].eccn)-xkmper;

		if ((acos(xkmper/(apogee+xkmper))+(lin*deg2rad)) > fabs(qth.stnlat*deg2rad))
			return 1;
		else
			return 0;
	}
}

char Decayed (int x, double time)
{
	/* This function returns a 1 if it appears that the
	   satellite pointed to by 'x' has decayed at the
	   time of 'time'.  If 'time' is 0.0, then the
	   current date/time is used. */

	double satepoch;

	if (time==0.0)
		time=CurrentDaynum();

	satepoch=DayNum(1,0,sat[x].year)+sat[x].refepoch;

	if (satepoch+((16.666666-sat[x].meanmo)/(10.0*fabs(sat[x].drag))) < time)
		return 1;
	else
		return 0;
}

char Geostationary (int x)
{
	/* This function returns a 1 if the satellite pointed
	   to by "x" appears to be in a geostationary orbit. */

	if ((fabs(sat[x].meanmo-omega_E)<0.0002) && (sat[x].incl<=0.15))
		return 1;
	else
		return 0;
}

double FindAOS (void)
{
	/* This function finds and returns the time of AOS (aostime). */

	aostime=0.0;

	if (AosHappens(indx) && Geostationary(indx)==0 && Decayed(indx,daynum)==0)
	{
		Calc();

		/* Get the satellite in range */

		while (sat_ele<-1.0)
		{
			daynum-=0.00035*(sat_ele*((sat_alt/8400.0)+0.46)-2.0);
			Calc();
		}

		/* Find AOS */

		while (aostime==0.0)
		{
			if (fabs(sat_ele)<0.03)
				aostime=daynum;
			else
			{
				daynum-=sat_ele*sqrt(sat_alt)/530000.0;
				Calc();
			}
		}
	}

	return aostime;
}

double FindLOS (void)
{
	lostime=0.0;

	if (Geostationary(indx)==0 && AosHappens(indx)==1 && Decayed(indx,daynum)==0)
	{
		Calc();

		do
		{
			daynum+=sat_ele*sqrt(sat_alt)/502500.0;
			Calc();

			if (fabs(sat_ele) < 0.03)
				lostime=daynum;

		} while (lostime==0.0);
	}

	return lostime;
}

double FindLOS2 (void)
{
	/* This function steps through the pass to find LOS.
	   FindLOS() is called to "fine tune" and return the result. */

	do
	{
		daynum+=cos((sat_ele-1.0)*deg2rad)*sqrt(sat_alt)/25000.0;
		Calc();

	} while (sat_ele>=0.0);

	return(FindLOS());
}

double NextAOS (void)
{
	/* This function finds and returns the time of the next
	   AOS for a satellite that is currently in range. */

	aostime=0.0;

	if (AosHappens(indx) && Geostationary(indx)==0 && Decayed(indx,daynum)==0)
		daynum=FindLOS2()+0.014;  /* Move to LOS + 20 minutes */

	return (FindAOS());
}

int Print (char *string, int mode)
{
	/* This function buffers and displays orbital predictions
	   and allows screens to be saved to a disk file. */

	char type[20], spaces[80], head1[160], head2[70],
	     head3[72], satellite_name[25];
	int key, ans=0, l, x, t, button1=0, button3=0;
	static char buffer[1450], lines, quit;

	static FILE *fd;

	/* Pass a NULL string to initialize the buffer, counter, and flags */

	if (string[0]==0)
	{
		lines=0;
		quit=0;
		buffer[0]=0;
		fd=NULL;
	}

	else
	{
		if (mode=='m')
		{
			sprintf(head1,"\n                    %s's Orbit Calendar for the Moon",qth.callsign);
			strncpy2(satellite_name,"Moon\0",5);
		}

		if (mode=='o')
		{
			sprintf(head1,"\n                    %s's Orbit Calendar for the Sun",qth.callsign);
			strncpy2(satellite_name,"Sun\0",4);
		}
	
		if (mode=='m' || mode=='o')
			sprintf(head2,"\n\t   Date     Time    El   Az   RA     Dec    GHA     Vel   Range\n");

		if (mode=='p')
			strcpy(type,"Orbit");

		if (mode=='v')
			strcpy(type,"Visual");

		if (mode=='s')
			strcpy(type,"Solar Illumination");

		if (mode!='m' && mode!='o')
		{
			strcpy(satellite_name,sat[indx].name);

			l=strlen(qth.callsign)+strlen(satellite_name)+strlen(type);

			spaces[0]=0;

			for (x=l; x<60; x+=2)
				strcat(spaces," ");

			sprintf(head1,"\n%s%s's %s Calendar For %s", spaces, qth.callsign, type, satellite_name);

			if (mode=='s')
				sprintf(head2,"\n\t  Date     Mins/Day    Sun%c          Date      Mins/Day    Sun%c\n",37,37);
			else
			{
				if (calc_squint)

					sprintf(head2,"\n\t   Date     Time    El   Az  Phase  %s   %s    Range  Squint\n",(io_lat=='N'?"LatN":"LatS"),(io_lon=='W'?"LonW":"LonE"));
				else

					sprintf(head2,"\n\t   Date     Time    El   Az  Phase  %s   %s    Range  Orbit\n",(io_lat=='N'?"LatN":"LatS"),(io_lon=='W'?"LonW":"LonE"));
			}
		}

		sprintf(head3,"      -----------------------------------------------------------------");

		strcat(buffer,string);
		lines++;

		if (lines==18)
		{
			bkgdset(COLOR_PAIR(2)|A_BOLD);
			clear();
			attroff(COLOR_PAIR(2)|A_BOLD);

			addstr(head1);
			attrset(COLOR_PAIR(4)|A_BOLD);
			addstr(head2);
			addstr(head3);
			attrset(COLOR_PAIR(3)|A_BOLD);

			if (buffer[0]!='\n')
				printw("\n");

			addstr(buffer);
			attrset(COLOR_PAIR(4)|A_BOLD);

			if (buffer[0]=='\n')
				printw("\n");

			if (fd==NULL)
				mvprintw(23,63,"        ");
			else
				mvprintw(23,63,"Log = ON");

			mvprintw(23,6,"More? [y/n] >> ");
			curs_set(1);
			refresh();

			nodelay(stdscr,FALSE);	/* getch() waits until there's a keystroke */

			while (ans==0)
			{
				do
				{
					key=getch();

					if (key==KEY_MOUSE)
					{
						getmouse(&event);

						button1=(event.bstate&BUTTON1_CLICKED || event.bstate&BUTTON1_DOUBLE_CLICKED || event.bstate&BUTTON1_PRESSED || event.bstate&BUTTON1_RELEASED);
						button3=(event.bstate&BUTTON3_CLICKED || event.bstate&BUTTON3_DOUBLE_CLICKED || event.bstate&BUTTON3_PRESSED || event.bstate&BUTTON3_RELEASED);

						if (button1)
							key='Y';

						else if (button3)
							key='N';
					}
					else
						key=toupper(key);

				} while (key>128 && (button1==0 || button3==0));

				if (key=='Y' || key=='\n' || key==' ')
				{
					key='Y';
					ans=1;
					quit=0;
				}
			
				if (key=='N' || key=='Q' || key==27)
				{
					key='N';
					ans=1;
					quit=1;
				}

				if (key=='L' && fd==NULL && buffer[0])
				{
					snprintf(temp,80,"%s.txt",satellite_name);

					l=strlen(temp)-4;

					for (x=0; x<l; x++)
					{
						t=temp[x];

						if (t==32 || t==17 || t==92 || t==42 || t==46 || t==47)
							t='_';

						temp[x]=t;
					}

					mvprintw(23,63,"%s",temp);

					fd=fopen(temp,"a");

					if (fd!=NULL)
					{
						fprintf(fd,"%s%s%s\n",head1,head2,head3);
						fprintf(fd,"%s",buffer);
					}

					mvprintw(23,63,"Log = ON");
					move(23,21);
					refresh();
				}

				else if (fd!=NULL)
				{
					if (key=='L' || key=='N')
					{
						fprintf(fd,"%s\n\n",buffer);
						fclose(fd);
						fd=NULL;
						mvprintw(23,63,"        ");
						move(23,21);
						refresh();
					}

					else
						fprintf(fd,"%s",buffer);
				}
				buffer[0]=0;
			}

			lines=0;
			curs_set(0);
			nodelay(stdscr,TRUE);	/* getch() returns a value immediately */
		}
	}

	return (quit);
}

int PrintVisible (char *string)
{
	/* This function acts as a filter to display passes that could
	   possibly be optically visible to the ground station.  It works
	   by first buffering prediction data generated by the Predict()
	   function and then checking it to see if at least a part of the
	   pass is visible.  If it is, then the buffered prediction data
	   is sent to the Print() function so it can be displayed
	   to the user and optionally logged to a file. */

	static char buffer[10000];
	char line[80], plus, asterisk, visible;
	int x, y, quit=0;

	if (string[0]==0)
		buffer[0]=0;
	else
	{
		strcat(buffer,string);

		if (string[0]=='\n')
		{
			plus=0;
			visible=0;
			asterisk=0;

			for (x=0; buffer[x]!=0 && visible==0; x++)
			{
				if (buffer[x]=='+')
					plus++;

				if (buffer[x]=='*')
					asterisk++;

				/* At least 3 +'s or at least 2 +'s
				   combined with at least 2 *'s are
				   worth displaying as a visible pass. */

				if ((plus>3) || (plus>2 && asterisk>2))
					visible=1;
			}

			if (visible)
			{
				/* Dump buffer to Print() line by line */

				for (x=0, y=0; buffer[x]!=0 && quit==0; x++)
				{	
					line[y]=buffer[x];

					if (line[y]=='\n')
					{
						line[y+1]=0;
						quit=Print(line,'v');
						line[0]=0;
						y=0;
					}

					else
						y++;
				}
			}

			buffer[0]=0;
		}
	}

	return quit;
}

void Predict (int mode)
{
	/* This function predicts satellite passes.  It displays
	   output through the Print() function if mode=='p' (show
	   all passes), or through the PrintVisible() function if
	   mode=='v' (show optically visible passes only). */

	int ch=0, quit=0, lastel=0, breakout=0;
	char string[80], type[10];

	PreCalc(indx);
	daynum=GetStartTime(0);
	clear();

	/* Trap geostationary orbits and passes that cannot occur. */

	if (AosHappens(indx) && Geostationary(indx)==0 && Decayed(indx,daynum)==0)
	{
		if (xterm)
		{
			strcpy(type,"Orbit");  /* Default */

			if (mode=='v')
				strcpy(type,"Visual");

			fprintf(stderr,"\033]0;PREDICT: %s's %s Calendar For %s\007",qth.callsign, type, sat[indx].name);
		}

		do
		{
			daynum=FindAOS();
		
			/* Display the pass */

			while (iel>=0 && quit==0)
			{
				if (calc_squint)

					snprintf(string,80,"      %s%4d %4d  %4d  %4d   %4d   %6ld  %4.0f %c\n",Daynum2String(daynum),iel,iaz,ma256,(io_lat=='N'?+1:-1)*isplat,(io_lon=='W'?isplong:360-isplong),irk,squint,findsun);

				else
					snprintf(string,80,"      %s%4d %4d  %4d  %4d   %4d   %6ld  %6ld %c\n",Daynum2String(daynum),iel,iaz,ma256,(io_lat=='N'?+1:-1)*isplat,(io_lon=='W'?isplong:360-isplong),irk,rv,findsun);

				lastel=iel;

				if (mode=='p')
					quit=Print(string,'p');

				if (mode=='v')
				{
					attrset(COLOR_PAIR(4));
					mvprintw(23,6,"                 Calculating... Press [ESC] To Quit");

					/* Allow a way out if this
					   should continue forever... */

					nodelay(stdscr,TRUE);	/* getch() returns a value immediately */

					ch=getch();

					if (ch==KEY_MOUSE)
					{
						getmouse(&event);

						if (event.bstate&BUTTON3_CLICKED || event.bstate&BUTTON3_DOUBLE_CLICKED || event.bstate&BUTTON3_PRESSED || event.bstate&BUTTON3_RELEASED)
							breakout=1;
					}
					else if (ch==27)
						breakout=1;

					nodelay(stdscr,FALSE);	/* getch() waits until there's a keystroke */

					quit=PrintVisible(string);
				}

				daynum+=cos((sat_ele-1.0)*deg2rad)*sqrt(sat_alt)/25000.0;
				Calc();
			}

			if (lastel!=0)
			{
				daynum=FindLOS();
				Calc();

				if (calc_squint)
					snprintf(string,80,"      %s%4d %4d  %4d  %4d   %4d   %6ld  %4.0f %c\n",Daynum2String(daynum),iel,iaz,ma256,(io_lat=='N'?+1:-1)*isplat,(io_lon=='W'?isplong:360-isplong),irk,squint,findsun);

				else
					snprintf(string,80,"      %s%4d %4d  %4d  %4d   %4d   %6ld  %6ld %c\n",Daynum2String(daynum),iel,iaz,ma256,(io_lat=='N'?+1:-1)*isplat,(io_lon=='W'?isplong:360-isplong),irk,rv,findsun);

				if (mode=='p')
					quit=Print(string,'p');

				if (mode=='v')
					quit=PrintVisible(string);
			}

			if (mode=='p')
				quit=Print("\n",'p');

			if (mode=='v')
				quit=PrintVisible("\n");

			/* Move to next orbit */
			daynum=NextAOS();

		}  while (quit==0 && breakout==0 && AosHappens(indx) && Decayed(indx,daynum)==0);
	}

	else
	{
		bkgdset(COLOR_PAIR(5)|A_BOLD);
		clear();
		attroff(COLOR_PAIR(5)|A_BOLD);

		if (AosHappens(indx)==0 || Decayed(indx,daynum)==1)
			mvprintw(12,5,"*** Passes for %s cannot occur over your ground station! ***\n",sat[indx].name);

		if (Geostationary(indx)==1)
			mvprintw(12,3,"*** Orbital predictions cannot be made for a geostationary satellite! ***\n");

		beep();
		bkgdset(COLOR_PAIR(7)|A_BOLD);
		AnyKey();
		refresh();
	}
}

void PredictMoon (void)
{
	/* This function predicts "passes" of the Moon */

	int iaz, iel, lastel=0;
	char string[1630], quit=0;
	double daynum, lastdaynum, moonrise=0.0;

	daynum=GetStartTime('m');
	clear();

	if (xterm)
		fprintf(stderr,"\033]0;PREDICT: %s's Orbit Calendar for the Moon\007",qth.callsign);

	do
	{
		/* Determine moonrise */

		FindMoon(daynum);

		while (moonrise==0.0)
		{
			if (fabs(moon_el)<0.03)
				moonrise=daynum;
			else
			{
				daynum-=(0.004*moon_el);
				FindMoon(daynum);
			}
		}

		FindMoon(moonrise);
		daynum=moonrise;
		iaz=(int)rint(moon_az);
		iel=(int)rint(moon_el);

		do
		{
			/* Display pass of the moon */

			snprintf(string,1625,"      %s%4d %4d  %5.1f  %5.1f  %5.1f  %6.1f%7.3f\n",Daynum2String(daynum), iel, iaz, moon_ra, moon_dec, moon_gha, moon_dv, moon_dx);
			string[80]=0;
			quit=Print(string,'m');
			lastel=iel;
			lastdaynum=daynum;
			daynum+=0.04*(cos(deg2rad*(moon_el+0.5)));
			FindMoon(daynum);
			iaz=(int)rint(moon_az);
			iel=(int)rint(moon_el);

		} while (iel>3 && quit==0);

		while (lastel!=0 && quit==0)
		{
			daynum=lastdaynum;

			do
			{
				/* Determine setting time */

				daynum+=0.004*(sin(deg2rad*(moon_el+0.5)));
				FindMoon(daynum);
				iaz=(int)rint(moon_az);
				iel=(int)rint(moon_el);

			} while (iel>0);

			/* Print moonset */

			snprintf(string,1625,"      %s%4d %4d  %5.1f  %5.1f  %5.1f  %6.1f%7.3f\n",Daynum2String(daynum), iel, iaz, moon_ra, moon_dec, moon_gha, moon_dv, moon_dx);
			string[80]=0;
			quit=Print(string,'m');
			lastel=iel;
		}

		quit=Print("\n",'m');
		daynum+=0.4;
		moonrise=0.0;

	} while (quit==0);
}

void PredictSun (void)
{
	/* This function predicts "passes" of the Sun. */

	int iaz, iel, lastel=0;
	char string[1630], quit=0;
	double daynum, lastdaynum, sunrise=0.0;

	daynum=GetStartTime('o');
	clear();

	if (xterm)
		fprintf(stderr,"\033]0;PREDICT: %s's Orbit Calendar for the Sun\007",qth.callsign);

	do
	{
		/* Determine sunrise */

		FindSun(daynum);

		while (sunrise==0.0)
		{
			if (fabs(sun_ele)<0.03)
				sunrise=daynum;
			else
			{
				daynum-=(0.004*sun_ele);
				FindSun(daynum);
			}
		}

		FindSun(sunrise);
		daynum=sunrise;
		iaz=(int)rint(sun_azi);
		iel=(int)rint(sun_ele);

		/* Print time of sunrise */

		do
		{
			/* Display pass of the sun */

			snprintf(string,1625,"      %s%4d %4d  %5.1f  %5.1f  %5.1f  %6.1f%7.3f\n",Daynum2String(daynum), iel, iaz, sun_ra, sun_dec, sun_lon, sun_range_rate, sun_range);
			string[80]=0;
			quit=Print(string,'o');
			lastel=iel;
			lastdaynum=daynum;
			daynum+=0.04*(cos(deg2rad*(sun_ele+0.5)));
			FindSun(daynum);
			iaz=(int)rint(sun_azi);
			iel=(int)rint(sun_ele);

		} while (iel>3 && quit==0);

		while (lastel!=0 && quit==0)
		{
			daynum=lastdaynum;

			do
			{
				/* Find sun set */

				daynum+=0.004*(sin(deg2rad*(sun_ele+0.5)));
				FindSun(daynum);
				iaz=(int)rint(sun_azi);
				iel=(int)rint(sun_ele);

			} while (iel>0);

			/* Print time of sunset */

			snprintf(string,1625,"      %s%4d %4d  %5.1f  %5.1f  %5.1f  %6.1f%7.3f\n",Daynum2String(daynum), iel, iaz, sun_ra, sun_dec, sun_lon, sun_range_rate, sun_range);
			string[80]=0;
			quit=Print(string,'o');
			lastel=iel;
		}

		quit=Print("\n",'o');
		daynum+=0.4;
		sunrise=0.0;

	} while (quit==0);
}

void ShowOrbitData (void)
{
	/* This function permits displays a satellite's orbital
	   data.  The age of the satellite data is also provided. */

	int c, x, namelength, age;	
	double an_period, no_period, sma, c1, e2, satepoch;
	char days[]={"days"};

	x=Select();

	while (x>=0 && x<24)
	{
		if (sat[x].meanmo!=0.0)
		{
			bkgdset(COLOR_PAIR(2)|A_BOLD);
			clear();
			attroff(COLOR_PAIR(2)|A_BOLD);

			sma=331.25*exp(log(1440.0/sat[x].meanmo)*(2.0/3.0));
			an_period=1440.0/sat[x].meanmo;
			c1=cos(sat[x].incl*deg2rad);
			e2=1.0-(sat[x].eccn*sat[x].eccn); 
			no_period=(an_period*360.0)/(360.0+(4.97*pow((xkmper/sma),3.5)*((5.0*c1*c1)-1.0)/(e2*e2))/sat[x].meanmo);
			satepoch=DayNum(1,0,sat[x].year)+sat[x].refepoch;
			age=(int)floor(CurrentDaynum()-satepoch);

			if (age!=1)
				days[3]='s';
			else
				days[3]=0;

			namelength=strlen(sat[x].name);
			namelength+=strlen(sat[x].designator);

			printw("\n");

			for (c=45; c>namelength; c-=2)
				printw(" ");

			if (sat[x].designator[0])
				printw("%s / Catalog Number %u / Designator %s\n",sat[x].name,sat[x].catnum,sat[x].designator);
			else
				printw("Orbital Data For %s / Catalog Number %u\n",sat[x].name,sat[x].catnum);

			attrset(COLOR_PAIR(3)|A_BOLD);

			if (age==0)
				printw("\n\t      Issued within the last day on %s UTC\n\n",Daynum2String(satepoch));
			else
				printw("\n\t\t  Issued %d %s ago on %s UTC\n\n",age,days,Daynum2String(satepoch));

			attrset(COLOR_PAIR(4)|A_BOLD);
			mvprintw(5,21,"Reference Epoch");
			mvprintw(6,21,"Inclination");
			mvprintw(7,21,"RAAN");
			mvprintw(8,21,"Eccentricity");
			mvprintw(9,21,"Arg of Perigee");
			mvprintw(10,21,"Mean Anomaly");
			mvprintw(11,21,"Mean Motion");
			mvprintw(12,21,"Decay Rate");
			mvprintw(13,21,"Nddot/6 Drag");
			mvprintw(14,21,"Bstar Drag Factor");
			mvprintw(15,21,"Semi-Major Axis");
			mvprintw(16,21,"Apogee Altitude");
			mvprintw(17,21,"Perigee Altitude");
			mvprintw(18,21,"Anomalistic Period");
			mvprintw(19,21,"Nodal Period");
			mvprintw(20,21,"Orbit Number");
			mvprintw(21,21,"Element Set Number");

			attrset(COLOR_PAIR(2)|A_BOLD);
			mvprintw(5,40,": %02d %.8f",sat[x].year,sat[x].refepoch);
			mvprintw(6,40,": %.4f deg",sat[x].incl);
			mvprintw(7,40,": %.4f deg",sat[x].raan);
			mvprintw(8,40,": %g",sat[x].eccn);
			mvprintw(9,40,": %.4f deg",sat[x].argper);
			mvprintw(10,40,": %.4f deg",sat[x].meanan);
			mvprintw(11,40,": %.8f rev/day",sat[x].meanmo);
			mvprintw(12,40,": %g rev/day/day",sat[x].drag);
			mvprintw(13,40,": %g rev/day/day/day",sat[x].nddot6);
			mvprintw(14,40,": %g 1/earth radii",sat[x].bstar);
			mvprintw(15,40,": %.4f km",sma);
			mvprintw(16,40,": %.4f km",sma*(1.0+sat[x].eccn)-xkmper);
			mvprintw(17,40,": %.4f km",sma*(1.0-sat[x].eccn)-xkmper);
			mvprintw(18,40,": %.4f mins",an_period);
			mvprintw(19,40,": %.4f mins",no_period);
			mvprintw(20,40,": %u",sat[x].orbitnum);
			mvprintw(21,40,": %u",sat[x].setnum);

			attrset(COLOR_PAIR(3)|A_BOLD);
			refresh();
			AnyKey();
		}

		x=Select();
	 };
}

void KepEdit (void)
{
	/* This function permits keyboard editing of the orbital database. */

	int x;
	char tmp[80];

	do
	{
		x=Select();

		if (x!=-1)
		{
			bkgdset(COLOR_PAIR(3)|A_BOLD);
			clear();
			attroff(COLOR_PAIR(3)|A_BOLD);

			mvprintw(3,1,"\t\t   *  Orbital Database Editing Utility  *\n\n");
			attrset(COLOR_PAIR(4)|A_BOLD);

			printw("\n\t\t\tSpacecraft Name :");
			printw("\n\t\t\tCatalog Number  :");
			printw("\n\t\t\tDesignator      :");
			printw("\n\t\t\tReference Epoch :");
			printw("\n\t\t\tInclination     :");
			printw("\n\t\t\tRAAN            :");
			printw("\n\t\t\tEccentricity    :");
			printw("\n\t\t\tArg of Perigee  :");
			printw("\n\t\t\tMean Anomaly    :");
			printw("\n\t\t\tMean Motion     :");
			printw("\n\t\t\tDecay Rate      :");
			printw("\n\t\t\tNddot/6         :");
			printw("\n\t\t\tBstar Drag Term :");        
			printw("\n\t\t\tOrbit Number    :");
			printw("\n\t\t\tElement Set No. :");

			attrset(COLOR_PAIR(2)|A_BOLD);

			mvprintw(6,42,"%s",sat[x].name);
			mvprintw(7,42,"%u",sat[x].catnum);
			mvprintw(8,42,"%s",sat[x].designator);
			mvprintw(9,42,"%2d %.8f",sat[x].year,sat[x].refepoch);
			mvprintw(10,42,"%.4f",sat[x].incl);
			mvprintw(11,42,"%.4f",sat[x].raan);
			mvprintw(12,42,"%g",sat[x].eccn);
			mvprintw(13,42,"%.4f",sat[x].argper);
			mvprintw(14,42,"%.4f",sat[x].meanan);
			mvprintw(15,42,"%.8f",sat[x].meanmo);
			mvprintw(16,42,"%g",sat[x].drag);
			mvprintw(17,42,"%g",sat[x].nddot6);
			mvprintw(18,42,"%g",sat[x].bstar);
			mvprintw(19,42,"%u",sat[x].orbitnum);
			mvprintw(20,42,"%u",sat[x].setnum);

			curs_set(0);
			refresh();

			snprintf(tmp,24,"%s",sat[x].name);

			if (LineEdit(42,6,tmp))
				strncpy2(sat[x].name,tmp,24);

			snprintf(tmp,10,"%u",sat[x].catnum);

			if (LineEdit(42,7,tmp))
				sscanf(tmp,"%u",&sat[x].catnum);

			snprintf(tmp,15,"%s",sat[x].designator);

			if (LineEdit(42,8,tmp))
				sscanf(tmp,"%s",sat[x].designator);

			snprintf(tmp,25,"%u %4.8f",sat[x].year,sat[x].refepoch);

			if (LineEdit(42,9,tmp))
				sscanf(tmp,"%u %lf",&sat[x].year,&sat[x].refepoch);

			snprintf(tmp,15,"%4.4f",sat[x].incl);

			if (LineEdit(42,10,tmp))
				sscanf(tmp,"%lf",&sat[x].incl);
			    
			snprintf(tmp,15,"%4.4f",sat[x].raan);

			if (LineEdit(42,11,tmp))
				sscanf(tmp,"%lf",&sat[x].raan);

			snprintf(tmp,15,"%g",sat[x].eccn);

			if (LineEdit(42,12,tmp))
				sscanf(tmp,"%lf",&sat[x].eccn);
			    
			snprintf(tmp,15,"%4.4f",sat[x].argper);

			if (LineEdit(42,13,tmp))
				sscanf(tmp,"%lf",&sat[x].argper);
			    
			snprintf(tmp,15,"%4.4f",sat[x].meanan);

			if (LineEdit(42,14,tmp))
				sscanf(tmp,"%lf",&sat[x].meanan);

			snprintf(tmp,15,"%4.8f",sat[x].meanmo);

			if (LineEdit(42,15,tmp))
				sscanf(tmp,"%lf",&sat[x].meanmo);
			    
			snprintf(tmp,15,"%g",sat[x].drag);

			if (LineEdit(42,16,tmp))
				sscanf(tmp,"%lf",&sat[x].drag);
			    
			snprintf(tmp,15,"%g",sat[x].nddot6);

			if (LineEdit(42,17,tmp))
				sscanf(tmp,"%lf",&sat[x].nddot6);

			snprintf(tmp,15,"%g",sat[x].bstar);

			if (LineEdit(42,18,tmp))
				sscanf(tmp,"%lf",&sat[x].bstar);

			snprintf(tmp,15,"%u",sat[x].orbitnum);

			if (LineEdit(42,19,tmp))
				sscanf(tmp,"%u",&sat[x].orbitnum);

			snprintf(tmp,15,"%u",sat[x].setnum);

			if (LineEdit(42,20,tmp))
				sscanf(tmp,"%u",&sat[x].setnum);
		}

	} while (x!=-1);

	if (resave)
	{
		SaveKEP();
		resave=0;
	}
}	

void QthEdit (void)
{
	char tmp[80];

	/* This function permits keyboard editing of
	   the ground station's location information. */

	bkgdset(COLOR_PAIR(3)|A_BOLD);
	clear();
	attroff(COLOR_PAIR(3)|A_BOLD);

	curs_set(0);	

	mvprintw(7,0,"\t\t *  Ground Station Location Editing Utility  *\n\n\n");

	attrset(COLOR_PAIR(4)|A_BOLD);
	printw("\n\t\t\tStation Callsign  :");
	printw("\n\t\t\tStation Latitude  :");
	printw("\n\t\t\tStation Longitude :");
	printw("\n\t\t\tStation Altitude  :");

	attrset(COLOR_PAIR(2)|A_BOLD);
	mvprintw(11,44,"%s",qth.callsign);

	if (io_lat=='N')
		mvprintw(12,44,"%.8g",+qth.stnlat);
	else
		mvprintw(12,44,"%.8g",-qth.stnlat);

	if (io_lon=='W')
		mvprintw(13,44,"%.8g",+qth.stnlong);
	else
		mvprintw(13,44,"%.8g",-qth.stnlong);

	mvprintw(14,44,"%d",qth.stnalt);

	refresh();

	mvprintw(18,12,"Enter the callsign or identifier of your ground station");
	snprintf(tmp,15,"%s",qth.callsign); 

	if (LineEdit(44,11,tmp))
		strncpy2(qth.callsign,tmp,15);

	if (io_lat=='N')
		sprintf(tmp,"%.8g",+qth.stnlat);
	else
		sprintf(tmp,"%.8g",-qth.stnlat);

	if (io_lat=='N')
		mvprintw(18,12,"Enter your latitude in degrees NORTH  (south=negative) ");
	else
		mvprintw(18,12,"Enter your latitude in degrees SOUTH  (north=negative) ");
 
	mvprintw(19,12,"  Decimal (74.2467) or DMS (74 14 48) format allowed");

	if (LineEdit(44,12,tmp))
	{
		if (io_lat=='N')
			qth.stnlat=+ReadBearing(tmp);
		else
			qth.stnlat=-ReadBearing(tmp);
	}
 
	if (io_lon=='W')
		snprintf(tmp,80,"%.8g",+qth.stnlong);
	else
		snprintf(tmp,80,"%.8g",-qth.stnlong);
 
	if (io_lon=='W')
		mvprintw(18,12,"Enter your longitude in degrees WEST   (east=negative) ");
	else
		mvprintw(18,12,"Enter your longitude in degrees EAST   (west=negative) ");
 
	if (LineEdit(44,13,tmp))
	{
		if (io_lon=='W')
			qth.stnlong=+ReadBearing(tmp);
		else
			qth.stnlong=-ReadBearing(tmp);
	}
 
	move(19,12);
	clrtoeol();
	mvprintw(18,12,"    Enter your altitude above sea level (in meters)   ");

	snprintf(tmp,80,"%d",qth.stnalt);

	if (LineEdit(44,14,tmp))
		qth.stnalt=atoi(tmp);

	if (resave)
	{
		saveQTH();
		resave=0;
	}
}

void SingleTrack (int x, int speak)
{
	/* This function tracks a single satellite in real-time
	   until 'Q' or ESC is pressed.  x represents the index
	   of the satellite being tracked.  If speak=='T', then
	   the speech routines are enabled. */

	int	ans, i, oldaz=0, oldel=0, length=0, xponder=0, button1=0,
		polarity=0, tshift, bshift, spaces, namelength, result,
		button3=0, thisdaynum;
	char	approaching=0, command[80], comsat, aos_alarm=0,
		geostationary=0, aoshappens=0, decayed=0, alstr[80],
		eclipse_alarm=0, visibility=0, old_visibility=0,
		change=0;
	double	oldtime=0.0, nextaos=0.0, lostime=0.0, aoslos=0.0,
		downlink=0.0, uplink=0.0, downlink_start=0.0, eta=0.0,
		downlink_end=0.0, uplink_start=0.0, uplink_end=0.0,
		dopp, doppler100=0.0, delay, loss, shift, vel=0.0;
	long	newtime, lasttime=0;

	pthread_t t1;

	PreCalc(x);
	indx=x;

	if (sat_xponder[x].transponders>0)
	{
		comsat=1;
		tshift=0;
		bshift=0;
	}

	else
	{
		comsat=0;
		tshift=2;
		bshift=-2;
	}

	daynum=CurrentDaynum();

	if (comsat)
	{
		for (i=0, xponder=0; i<sat_xponder[indx].transponders; i++)
		{
			if (sat_xponder[indx].phase_start[i]!=0 && sat_xponder[indx].phase_end[i]!=0)
			{
				if (sat_xponder[indx].phase_start[i]>=(256.0*(phase/twopi)) && sat_xponder[indx].phase_end[i]<=(256.0*(phase/twopi)))
				{
					xponder=i;
					break;
				}
			}
		}

		thisdaynum=1+((int)daynum)%7;

		for (i=0; i<sat_xponder[indx].transponders; i++)
		{
			if (sat_xponder[indx].dayofweek[i]==thisdaynum)
			{
				xponder=i;
				break;
			}
		}

		downlink_start=sat_xponder[x].downlink_start[xponder];
		downlink_end=sat_xponder[x].downlink_end[xponder];
		uplink_start=sat_xponder[x].uplink_start[xponder];
		uplink_end=sat_xponder[x].uplink_end[xponder];

		if (downlink_start>downlink_end)
			polarity=-1;

		if (downlink_start<downlink_end)
			polarity=1;

		if (downlink_start==downlink_end)
			polarity=0;

		downlink=0.5*(downlink_start+downlink_end);
		uplink=0.5*(uplink_start+uplink_end);
	}

	aoshappens=AosHappens(indx);
	geostationary=Geostationary(indx);
	decayed=Decayed(indx,0.0);
	alstr[0]=0;

	if (xterm)
		fprintf(stderr,"\033]0;PREDICT: Tracking %-10s\007",sat[x].name); 

	halfdelay(2);
	curs_set(0);
	bkgdset(COLOR_PAIR(3));
	clear();

	/* Keep the Android OS awake if we're providing real-time data to clients */

	if (android && awake==0)
	{
		system("/data/data/com.termux/files/usr/bin/termux-wake-lock &");
		awake=1;
	}

	/* Start displaying headings and information */

	do
	{
		attroff(COLOR_PAIR(3));
		attrset(COLOR_PAIR(5)|A_BOLD);

		mvprintw(0,0,"                                                                                 ");
		mvprintw(1,0,"                     PREDICT Real-Time Satellite Tracking                        ");

		namelength=strlen(sat[x].name);
		spaces=22-namelength;

		mvprintw(2,0,"                                                                                 ");

		mvprintw(2,spaces,"Tracking: %s",sat[x].name);

		mvprintw(3,0,"                                                                                 ");

		attrset(COLOR_PAIR(4)|A_BOLD);

		mvprintw(4,0,"                                                                                 ");

		mvprintw(5+tshift,1,"Satellite     Direction     Velocity     Footprint    Altitude     Slant Range");
		mvprintw(6+tshift,1,"---------     ---------     --------     ---------    --------     -----------");
		mvprintw(7+tshift,1,"        .            Az           mi            mi          mi              mi");
		mvprintw(8+tshift,1,"        .            El           km            km          km              km");
		mvprintw(16+bshift,1,"Eclipse Depth   Orbital Phase   Orbital Model   Squint Angle      AutoTracking");
		mvprintw(17+bshift,1,"-------------   -------------   -------------   ------------      ------------");

		if (comsat)
		{
			mvprintw(12,1,"Uplink   :");
			mvprintw(13,1,"Downlink :");
			mvprintw(14,1,"Delay    :");
			mvprintw(14,55,"Echo      :");
			mvprintw(13,29,"RX:");
			mvprintw(13,55,"Path loss :");
			mvprintw(12,29,"TX:");
			mvprintw(12,55,"Path loss :");
		}

		attrset(COLOR_PAIR(5)|A_BOLD);
		daynum=CurrentDaynum();

		mvprintw(2,55-spaces,"On: %s",Daynum2String(daynum));

		attrset(COLOR_PAIR(2)|A_BOLD);
		Calc();

		mvprintw(7+tshift,1,"%-6.2f",(io_lat=='N'?+1:-1)*sat_lat);
		attrset(COLOR_PAIR(4)|A_BOLD);
		mvprintw(7+tshift,8,(io_lat=='N'?"N":"S"));
		mvprintw(8+tshift,8,(io_lon=='W'?"W":"E"));

		fk=12756.33*acos(xkmper/(xkmper+sat_alt));
		fm=fk*km2mi;

		attrset(COLOR_PAIR(2)|A_BOLD);

		mvprintw(7+tshift,55,"%0.f ",sat_alt*km2mi);
		mvprintw(8+tshift,55,"%0.f ",sat_alt);
		mvprintw(7+tshift,68,"%-5.0f",sat_range*km2mi);
		mvprintw(8+tshift,68,"%-5.0f",sat_range);

		mvprintw(8+tshift,1,"%-7.2f",(io_lon=='W'?360.0-sat_lon:sat_lon));
		mvprintw(7+tshift,15,"%-7.2f",sat_azi);
		mvprintw(8+tshift,14,"%+-6.2f",sat_ele);
		mvprintw(7+tshift,29,"%0.f ",(3600.0*sat_vel)*km2mi);
		mvprintw(8+tshift,29,"%0.f ",3600.0*sat_vel);

		mvprintw(18+bshift,3,"%+6.2f",eclipse_depth/deg2rad);
		addch(ACS_DEGREE);
		addstr("  ");
		mvprintw(18+bshift,20,"%5.1f",256.0*(phase/twopi));
		mvprintw(18+bshift,37,"%s",ephem);

		if (sat_sun_status)
		{
			visibility_array[indx]='D';

			if (sat_ele>=0.0)
			{
				if ((sun_ele<=-12.0) || (sun_ele<=ISS_EL_LIMIT && sat[indx].catnum==25544))	
					visibility_array[indx]='V';
			}
		}
		else
			visibility_array[indx]='N';

		visibility=visibility_array[indx];

		if (comsat)
		{
			if (downlink!=0.0)
				mvprintw(13,11,"%11.5f MHz",downlink);

			else
				mvprintw(13,11,"               ");

			if (uplink!=0.0)
				mvprintw(12,11,"%11.5f MHz",uplink);

			else
				mvprintw(12,11,"               ");
		}

		if (rotctld_socket!=-1)
		{
			/* Find minutes to ETA.  This may be a
			   huge negatve number if we jump into
			   SingleTrack() when the satellite is
			   in range, but the rotator will still
			   be activated normally. */

			eta=1440.0*(nextaos-daynum);

			if (aoslos!=0.0 && eta<2.0)
			{
				mvprintw(18+bshift,67,"   Active   ");

				/* Send data to rotctld either as needed
				   (when it changes), or once per second. */

				newtime=(long)time(NULL);

				if ((oldel!=iel || oldaz!=iaz) || (once_per_second && newtime>lasttime))
				{
					if (rotctld_socket!=-1)
					{
						if (iel<0)
							TrackDataNet(rotctld_socket,0.0,sat_azi);
						else
							TrackDataNet(rotctld_socket,sat_ele,sat_azi);
					}

					oldel=iel;
					oldaz=iaz;
					lasttime=newtime;
				}
			}

			else
				mvprintw(18+bshift,67,"Standing  By");
		}

		else
			mvprintw(18+bshift,67,"Not  Enabled");

		if (calc_squint)
			mvprintw(18+bshift,52,"%+6.2f",squint);
		else
			mvprintw(18+bshift,54,"N/A");

		doppler100=-100.0e06*((sat_range_rate*1000.0)/299792458.0);
		delay=1000.0*((1000.0*sat_range)/299792458.0);

		if (sat_ele>=0.0)
		{
			/* Satellite is above the horizon */

			if (aos_alarm==0)
			{
				beep();
				aos_alarm=1;
			}

			attrset(COLOR_PAIR(4)|A_BOLD);

			if (comsat==0)
			{
				if (fabs(sat_range_rate)<0.1)
					mvprintw(19,28,"         TCA            ");
				else
				{
					if (sat_range_rate<0.0)
						mvprintw(19,28,"Satellite is Approaching");

					if (sat_range_rate>0.0)
						mvprintw(19,28," Satellite is Receding  ");
				}
			}	

			else
			{
				if (fabs(sat_range_rate)<0.1)
					mvprintw(14,34,"    TCA    ");

				else
				{
					if (sat_range_rate<0.0)
						mvprintw(14,34,"Approaching");

					if (sat_range_rate>0.0)
						mvprintw(14,34,"  Receding ");
				}

				attrset(COLOR_PAIR(2)|A_BOLD);

				if (downlink!=0.0)
				{
					dopp=1.0e-08*(doppler100*downlink);
					mvprintw(13,32,"%11.5f MHz",downlink+dopp);
					loss=32.4+(20.0*log10(downlink))+(20.0*log10(sat_range));
					mvprintw(13,67,"%7.3f dB",loss);
					mvprintw(14,13,"%7.3f   ms",delay);
				}

				else
				{
					mvprintw(13,32,"                ");
					mvprintw(13,67,"          ");
					mvprintw(14,13,"            ");
				}

				if (uplink!=0.0)
				{
					dopp=1.0e-08*(doppler100*uplink);
					mvprintw(12,32,"%11.5f MHz",uplink-dopp);
					loss=32.4+(20.0*log10(uplink))+(20.0*log10(sat_range));
					mvprintw(12,67,"%7.3f dB",loss);
				}

				else
				{
					mvprintw(12,32,"                ");
					mvprintw(12,67,"          ");
				}

				if (uplink!=0.0 && downlink!=0.0)
					mvprintw(14,67,"%7.3f ms",2.0*delay);

				else
					mvprintw(14,67,"              ");
			}

			if (speak=='T' && SOUNDCARD && sat_ele>=0.0)
			{
				if (eclipse_alarm==0 && fabs(eclipse_depth)<0.015) /* ~1 deg */
				{
					/* Hold off regular announcements if
					   satellite is within about 2 degrees
					   of entering into or out of an
					   eclipse. */

					oldtime=CurrentDaynum();

					if ((old_visibility=='V' || old_visibility=='D') && visibility=='N' && talking==0)
					{
						result=pthread_create(&t1, NULL, vocalizer, "eclipse");
						assert(!result);

						eclipse_alarm=1;
						oldtime-=0.000015*sqrt(sat_alt);
					}

					if (old_visibility=='N' && (visibility=='V' || visibility=='D') && talking==0)
					{
						result=pthread_create(&t1, NULL, vocalizer, "sunlight");
						assert(!result);

						eclipse_alarm=1;
						oldtime-=0.000015*sqrt(sat_alt);
					}
				}

				if ((CurrentDaynum()-oldtime)>(0.00003*sqrt(sat_alt))&&talking==0)
				{
					if (sat_range_rate<0.0)
					{
						approaching='+';
						vel=ceil(sat_ele);
					}

					else if (sat_range_rate>0.0)
					{
						approaching='-';
						vel=floor(sat_ele);
					}
					else
						vel=sat_ele;

					if (sat_ele<0.5)
						vel=0.0;

					sprintf(command,"%d %.0f %c %c",iaz,vel,approaching,visibility);
					result=pthread_create(&t1, NULL, vocalizer, command);
					assert(!result);
					
  					oldtime=CurrentDaynum();
					old_visibility=visibility;
				}

				if (sat_ele<=1.0 && approaching=='-')
				{
					/* Suspend regular announcements
					   as we approach LOS. */

					oldtime=CurrentDaynum();
				}
			}
		}

		else
		{
			/* Satellite is below the horizon */

			lostime=0.0;
			aos_alarm=0;
			eclipse_alarm=0;

			if (comsat)
			{
				mvprintw(12,32,"                ");
				mvprintw(12,67,"          ");
				mvprintw(13,32,"                ");
				mvprintw(13,67,"          ");
				mvprintw(14,13,"            ");
				mvprintw(14,34,"           ");
				mvprintw(14,67,"          ");
			}

			else
				mvprintw(19,28,"                       ");
		}

		mvprintw(7+tshift,42,"%0.f ",fm);
		mvprintw(8+tshift,42,"%0.f ",fk);

		attrset(COLOR_PAIR(3)|A_BOLD);

		mvprintw(21,22,"Orbit Number: %ld",rv);

		mvprintw(23,22,"Spacecraft is currently ");

		if (visibility=='V')
			mvprintw(23,46,"visible    ");

		if (visibility=='D')
			mvprintw(23,46,"in sunlight");

		if (visibility=='N')
			mvprintw(23,46,"in eclipse ");

		attrset(COLOR_PAIR(4)|A_BOLD);
		mvprintw(20,5,"   Sun   ");
		mvprintw(21,5,"---------");
		attrset(COLOR_PAIR(3)|A_BOLD);
		mvprintw(22,5,"%-7.2fAz",sun_azi);
		mvprintw(23,4,"%+-6.2f  El",sun_ele);

		FindMoon(daynum);

		attrset(COLOR_PAIR(4)|A_BOLD);
		mvprintw(20,65,"  Moon  ");
		mvprintw(21,65,"---------");
		attrset(COLOR_PAIR(3)|A_BOLD);
		mvprintw(22,65,"%-7.2fAz",moon_az);
		mvprintw(23,64,"%+-6.2f  El",moon_el);


		if (geostationary==1 && sat_ele>=0.0)
		{
			sprintf(alstr,"Satellite orbit is geostationary");
			// aoslos=-3651.0;
			aoslos=0.0;
		}

		if (geostationary==1 && sat_ele<0.0)
		{
			sprintf(alstr,"This satellite never reaches AOS");
			// aoslos=-3651.0;
			aoslos=0.0;
		}

		if (aoshappens==0 || decayed==1)
		{
			sprintf(alstr,"This satellite never reaches AOS");
			// aoslos=-3651.0;
			aoslos=0.0;
		}

		if (sat_ele>=0.0 && geostationary==0 && decayed==0 && daynum>lostime)
		{
			lostime=FindLOS2();
			sprintf(alstr,"LOS at: %s UTC  ",Daynum2String(lostime));
			aoslos=lostime;
		}

		else if (sat_ele<0.0 && geostationary==0 && decayed==0 && aoshappens==1 && daynum>aoslos)
		{
			daynum+=0.003;  /* Move ahead slightly... */
			nextaos=FindAOS();
			sprintf(alstr,"Next AOS: %s UTC",Daynum2String(nextaos));
			aoslos=nextaos;

			if (oldtime!=0.0 && speak=='T' && SOUNDCARD && talking==0)
			{
				/* Announce LOS */

				result=pthread_create(&t1, NULL, vocalizer, "los");
				assert(!result);
			}
		}

		/* Display the next AOS, LOS, etc. */

		mvprintw(22,22,"%s",alstr);

		/* Update the variables for the socket server. */

		if (socket_flag)
		{
			az_array[indx]=sat_azi;
			el_array[indx]=sat_ele;
			lat_array[indx]=sat_lat;
			long_array[indx]=360.0-sat_lon;
			footprint_array[indx]=fk;
			range_array[indx]=sat_range;
			altitude_array[indx]=sat_alt;
			velocity_array[indx]=sat_vel;
			orbitnum_array[indx]=rv;
			doppler[indx]=doppler100;
			nextevent[indx]=aoslos;
			eclipse_depth_array[indx]=eclipse_depth/deg2rad;
			phase_array[indx]=360.0*(phase/twopi);

			if (calc_squint)
				squint_array[indx]=squint;
			else
				squint_array[indx]=360.0;

			FindSun(daynum);

			sprintf(tracking_mode, "%s\n%c",sat[indx].name,0);
		}

		/* Get input from keyboard */

		ans=getch();

		if (ans==KEY_MOUSE)
		{
			getmouse(&event);

			button1=(event.bstate&BUTTON1_CLICKED || event.bstate&BUTTON1_DOUBLE_CLICKED || event.bstate&BUTTON1_PRESSED || event.bstate&BUTTON1_RELEASED);

			button3=(event.bstate&BUTTON3_CLICKED || event.bstate&BUTTON3_DOUBLE_CLICKED || event.bstate&BUTTON3_PRESSED || event.bstate&BUTTON3_RELEASED);

			if ((event.y==10) && (event.x>=(40-length)) && (event.x<=(40+length)))
			{
				if (button3)
					ans=KEY_LEFT;

				if (button1)
					ans=KEY_RIGHT;
			}

			else if (button3)
				ans='q';
		}

		/* We can force PREDICT to speak by pressing 'T' */

		if (ans=='t' || ans=='T')
		{
			oldtime=0.0;
			speak='T';
		}

		/* We can force PREDICT to be silent by pressing 'S' */

		if (ans=='s' || ans=='S')
			speak=0;

		/* If we receive a RELOAD_TLE or RELOAD_KEP command
		   through the socket connection or an 'r' through
		   the keyboard, reload the KEP file.  */

		if (reload_kep || ans=='r' || ans=='R')
		{
			ReadDataFiles();
			reload_kep=0;
		}

		if (comsat)
		{
			if (ans==' ' || ans==KEY_RIGHT)
			{
				xponder++;
				change=1;

				if (xponder>=sat_xponder[x].transponders)
					xponder=0;
			}

			if (ans==KEY_LEFT)
			{
				xponder--;
				change=1;

				if (xponder<0)
					xponder=(sat_xponder[x].transponders-1);
			}

			if (ans==KEY_HOME || ans==KEY_UP)
			{
				change=1;
				xponder=0;
			}

			if (ans==KEY_END || ans==KEY_DOWN)
			{
				change=1;
				xponder=(sat_xponder[x].transponders-1);
			}

			if (change)
			{
				move(10,1);
				clrtoeol();

				downlink_start=sat_xponder[x].downlink_start[xponder];
				downlink_end=sat_xponder[x].downlink_end[xponder];
				uplink_start=sat_xponder[x].uplink_start[xponder];
				uplink_end=sat_xponder[x].uplink_end[xponder];

				if (downlink_start>downlink_end)
					polarity=-1;

				if (downlink_start<downlink_end)
					polarity=1;

				if (downlink_start==downlink_end)
					polarity=0;

				downlink=0.5*(downlink_start+downlink_end);
				uplink=0.5*(uplink_start+uplink_end);

				change=0;
			}

			if (ans=='>' || ans=='.')
			{
				if (ans=='>')
					shift=0.001;  /* 1 kHz */
				else
					shift=0.0001; /* 100 Hz */

				/* Raise uplink frequency */

				uplink+=shift*(double)abs(polarity);
				downlink=downlink+(shift*(double)polarity);

				if (uplink>uplink_end)
				{
					uplink=uplink_start;
					downlink=downlink_start;
				}
			}

			if (ans=='<' || ans== ',')
			{
				if (ans=='<')
					shift=0.001;  /* 1 kHz */
				else
					shift=0.0001; /* 100 Hz */

				/* Lower uplink frequency */

				uplink-=shift*(double)abs(polarity);
				downlink=downlink-(shift*(double)polarity);

				if (uplink<uplink_start)
				{
					uplink=uplink_end;
					downlink=downlink_end;
				}
			}

			length=strlen(sat_xponder[x].transponder_name[xponder])/2;
			mvprintw(10,40-length,"%s",sat_xponder[x].transponder_name[xponder]);
		}

		refresh();

	} while (ans!='q' && ans!=27);

	sprintf(tracking_mode, "NONE\n%c",0);

	if (android && awake)
	{
		system("/data/data/com.termux/files/usr/bin/termux-wake-unlock &");
		awake=0;
	}

	nocbreak();  /* Leave halfdelay() mode */
	cbreak();    /* Return to normal cbreak() mode */
}

int MultiTrack (void)
{
	/* This function tracks all satellites in the program's
	   database simultaneously until 'Q' or ESC is pressed.
	   Satellites in range are HIGHLIGHTED.  Coordinates
	   for the Sun and Moon are also displayed. */

	int		x, y, z, ans, ret=0;
	unsigned char	satindex[24], inrange[24], sunstat=0, ok2predict[24];
	char		alstr[3][80];
	double		aos[24], aos2[24], temptime,
			nextcalctime=0.0, los[24], aoslos[24];

	if (xterm)
		fprintf(stderr,"\033]0;PREDICT: Multi-Satellite Tracking Mode\007");

	/* Keep the Android OS awake if we're providing real-time data to clients */

	if (android && awake==0)
	{		
		system("/data/data/com.termux/files/usr/bin/termux-wake-lock &");
		awake=1;
	}

	curs_set(0);
	attrset(COLOR_PAIR(5)|A_BOLD);
	clear();

	mvprintw(0,0,"                                                                                ");
	mvprintw(1,0,"                     PREDICT Real-Time Multi-Tracking Mode                      ");
	mvprintw(2,0,"                    Current Date/Time:                                          ");
	mvprintw(3,0,"                                                                                ");

	attrset(COLOR_PAIR(2)|A_REVERSE);

	mvprintw(4,0," Satellite  Az   El %s  %s  Range  | Satellite  Az   El %s  %s  Range   ",(io_lat=='N'?"LatN":"LatS"),(io_lon=='W'?"LonW":"LonE"),(io_lat=='N'?"LatN":"LatS"),(io_lon=='W'?"LonW":"LonE"));

	for (x=0; x<24; x++)
	{
		if (Geostationary(x)==0 && AosHappens(x)==1 && Decayed(x,0.0)!=1)
			ok2predict[x]=1;
		else
			ok2predict[x]=0;

		aoslos[x]=0.0;
		los[x]=0.0;
		aos[x]=0.0;
		aos2[x]=0.0;
	}

	halfdelay(2);  /* Increase if CPU load is too high */

	do
	{
		for (z=0; z<24; z++)
		{
			y=z/2;

			if (z%2)
			{
				indx=y+12;
				x=41;
			}

			else
			{
				indx=y;
				x=1;
			}

			if (sat[indx].meanmo!=0.0 && Decayed(indx,0.0)!=1)
			{
				daynum=CurrentDaynum();
				PreCalc(indx);
				Calc();

				if (sat_ele>=0.0)
				{
					attrset(COLOR_PAIR(2)|A_BOLD);
					inrange[indx]=1;
				}

				else
				{
					attrset(COLOR_PAIR(2));
					inrange[indx]=0;
				}

				if (sat_sun_status)
				{
					sunstat='D';

					if (sat_ele>=0.0)
					{
						if ((sun_ele<=-12.0) || (sun_ele<=ISS_EL_LIMIT && sat[indx].catnum==25544))
							sunstat='V';
					}
				}
				else
					sunstat='N';

				mvprintw(y+6,x,"%-10s%3.0f  %+3.0f  %3.0f   %3.0f %6.0f %c", Abbreviate(sat[indx].name,9),sat_azi,sat_ele,(io_lat=='N'?+1:-1)*sat_lat,(io_lon=='W'?360.0-sat_lon:sat_lon),sat_range,sunstat);

				if (socket_flag)
				{
					az_array[indx]=sat_azi;
					el_array[indx]=sat_ele;
					lat_array[indx]=sat_lat;
					long_array[indx]=360.0-sat_lon;
					footprint_array[indx]=fk;
					range_array[indx]=sat_range;
					altitude_array[indx]=sat_alt;
					velocity_array[indx]=sat_vel;
					orbitnum_array[indx]=rv;
					visibility_array[indx]=sunstat;
					eclipse_depth_array[indx]=eclipse_depth/deg2rad;
					phase_array[indx]=360.0*(phase/twopi);

					doppler[indx]=-100e06*((sat_range_rate*1000.0)/299792458.0);

					if (calc_squint)
						squint_array[indx]=squint;
					else
						squint_array[indx]=360.0;

					FindSun(daynum);
					sprintf(tracking_mode,"MULTI\n%c",0);
				}

				attrset(COLOR_PAIR(4)|A_BOLD);
				mvprintw(20,5,"   Sun   ");
				mvprintw(21,5,"---------");
				attrset(COLOR_PAIR(3)|A_BOLD);
				mvprintw(22,5,"%-7.2fAz",sun_azi);
				mvprintw(23,4,"%+-6.2f  El",sun_ele);

				FindMoon(daynum);

				attrset(COLOR_PAIR(4)|A_BOLD);
				mvprintw(20,65,"  Moon  ");
				mvprintw(21,65,"---------");
				attrset(COLOR_PAIR(3)|A_BOLD);
				mvprintw(22,65,"%-7.2fAz",moon_az);
				mvprintw(23,64,"%+-6.2f  El",moon_el);

				/* Calculate Next Event (AOS/LOS) Times */

				if (ok2predict[indx] && daynum>los[indx] && inrange[indx])
					los[indx]=FindLOS2();

				if (ok2predict[indx] && daynum>aos[indx])
				{
					if (inrange[indx])
						aos[indx]=NextAOS();
					else
						aos[indx]=FindAOS();
				}

				if (inrange[indx])
					aoslos[indx]=los[indx];
				else
					aoslos[indx]=aos[indx];

				if (socket_flag)
				{
					if (ok2predict[indx])
						nextevent[indx]=aoslos[indx];
					else
						// nextevent[indx]=-3651.0;
						nextevent[indx]=0.0;
				}

				aos2[indx]=aos[indx];
				satindex[indx]=indx;
			}

			if (Decayed(indx,0.0))
			{
				attrset(COLOR_PAIR(2));
				mvprintw(y+6,x,"%-10s---------- Decayed ---------", Abbreviate(sat[indx].name,9));

				if (socket_flag)
				{
					az_array[indx]=0.0;
					el_array[indx]=0.0;
					lat_array[indx]=0.0;
					long_array[indx]=0.0;
					footprint_array[indx]=0.0;
					range_array[indx]=0.0;
					altitude_array[indx]=0.0;
					velocity_array[indx]=0.0;
					orbitnum_array[indx]=0L;
					visibility_array[indx]='N';
					eclipse_depth_array[indx]=0.0;
					phase_array[indx]=0.0;
					doppler[indx]=0.0;
					squint_array[indx]=0.0;
					// nextevent[indx]=-3651.0;
					nextevent[indx]=0.0;
				}

				/* NEW!! If the satellite has decayed, ensure the AOS date/time is set to zero. */

				aos[indx]=0.0;
				aos2[indx]=0.0;
			}
 		}

		attrset(COLOR_PAIR(5)|A_BOLD);
		daynum=CurrentDaynum();
		mvprintw(2,39,"%s",Daynum2String(daynum));

		if (daynum>nextcalctime)
		{
			/* Bubble sort the AOS times */

			for (z=22; z>=0; z--)
				for (y=0; y<=z; y++)
					if (aos2[y]>=aos2[y+1])
					{
						temptime=aos2[y];
						aos2[y]=aos2[y+1];
						aos2[y+1]=temptime;

						x=satindex[y];
						satindex[y]=satindex[y+1];
						satindex[y+1]=x;
					}

			/* Generate a list of upcoming passes */

			for (x=0, y=0, z=-1; x<23 && y!=3; x++)
			{
				if (ok2predict[satindex[x]] && aos2[x]!=0.0)
				{
					sprintf(alstr[y],"%10s on %s UTC",Abbreviate(sat[(int)satindex[x]].name,9),Daynum2String(aos2[x]));

					if (z==-1)
						z=x;
					y++;
				}
			}

			if (z!=-1)
				nextcalctime=aos2[z];
		}

		/* Display the list of upcoming passes */

		attrset(COLOR_PAIR(4)|A_BOLD);
		mvprintw(19,31,"Upcoming Passes");
		mvprintw(20,31,"---------------");
		attrset(COLOR_PAIR(3)|A_BOLD);

		for (y=0; y<3; y++)
			mvprintw(y+21,19,"%s",alstr[y]);

		ans=getch();

		if (ans==KEY_MOUSE)
		{
			getmouse(&event);

			if (event.bstate&BUTTON3_CLICKED || event.bstate&BUTTON3_DOUBLE_CLICKED || event.bstate&BUTTON3_PRESSED || event.bstate&BUTTON3_RELEASED)
				ans='q';
			else if (event.bstate&BUTTON1_CLICKED || event.bstate&BUTTON1_DOUBLE_CLICKED || event.bstate&BUTTON1_PRESSED || event.bstate&BUTTON1_RELEASED)
			{
				if (event.x>0 && event.x<39)
				{
					if (event.y>=6 && event.y<=17)
						ret=(event.y-5);
				}

				if (event.x>40 && event.x<79)
				{
					if (event.y>=6 && event.y<=17)
						ret=(event.y+7);
				}
			}
		}

		/* If we receive a RELOAD_TLE or RELOAD_KEP command
		   through the socket connection, or an 'R' through
		   the keyboard, reload the KEP file.  */

		if (reload_kep || ans=='r' || ans=='R')
		{
			ReadDataFiles();
			reload_kep=0;
			nextcalctime=0.0;
		}

		refresh();

	} while (ans!='q' && ans!='Q' && ans!=27 && ret==0);

	sprintf(tracking_mode, "NONE\n%c",0);

	if (android && awake)
	{
		system("/data/data/com.termux/files/usr/bin/termux-wake-unlock &");
		awake=0;
	}

	nocbreak();  /* Leave halfdelay() mode */
	cbreak();    /* Return to normal cbreak() mode */	

	return (ret-1);
}

void Illumination (void)
{
	double startday, oneminute, sunpercent;
	int eclipses, minutes, quit, ch=0, breakout=0;
	char string1[365], string[725], datestring[25], count;

	oneminute=1.0/(24.0*60.0);

	PreCalc(indx);
	daynum=floor(GetStartTime(0));
	startday=daynum;
	count=0;

	nodelay(stdscr,TRUE);
	curs_set(0);
	clear();

	if (xterm)
		fprintf(stderr,"\033]0;PREDICT: %s's Solar Illumination Calendar For %s\007",qth.callsign, sat[indx].name);

	do
	{
		attrset(COLOR_PAIR(4));
		mvprintw(23,6,"                 Calculating... Press [ESC] To Quit");
		refresh();

		count++;
		daynum=startday;

		for (minutes=0, eclipses=0; minutes<1440; minutes++)
		{
			Calc();

			if (sat_sun_status==0)
				eclipses++;

			daynum=startday+(oneminute*(double)minutes);
		}

		sunpercent=((double)eclipses)/((double)minutes);
		sunpercent=100.0-(sunpercent*100.0);

		strcpy(datestring,Daynum2String(startday));
		datestring[11]=0;
		sprintf(string1,"      %s    %4d    %6.2f%c",datestring,1440-eclipses,sunpercent,37);

		/* Allow a quick way out */

		ch=getch();

		if (ch==KEY_MOUSE)
		{
			getmouse(&event);

			if (event.bstate&BUTTON3_CLICKED || event.bstate&BUTTON3_DOUBLE_CLICKED || event.bstate&BUTTON3_PRESSED || event.bstate&BUTTON3_RELEASED)
				breakout=1;
		}

		else if (ch==27)
			breakout=1;

		startday+=18.0;

		daynum=startday;

		for (minutes=0, eclipses=0; minutes<1440; minutes++)
		{
			Calc();

			if (sat_sun_status==0)
				eclipses++;

			daynum=startday+(oneminute*(double)minutes);
		}

		sunpercent=((double)eclipses)/((double)minutes);
		sunpercent=100.0-(sunpercent*100.0);

		strcpy(datestring,Daynum2String(startday));
		datestring[11]=0;
		sprintf(string,"%s\t %s    %4d    %6.2f%c\n",string1,datestring,1440-eclipses,sunpercent,37);
		quit=Print(string,'s');

		/* Allow a quick way out */

		ch=getch();

		if (ch==KEY_MOUSE)
		{
			getmouse(&event);

			if (event.bstate&BUTTON3_CLICKED || event.bstate&BUTTON3_DOUBLE_CLICKED || event.bstate&BUTTON3_PRESSED || event.bstate&BUTTON3_RELEASED)
				breakout=1;
		}

		else if (ch==27)
			breakout=1;

		if (count<18)
			startday-=17.0;
		else
		{
			count=0;
			startday+=1.0;
		}
	}

	while (quit!=1 && breakout==0 && Decayed(indx,daynum)==0);

	nodelay(stdscr,FALSE);  /* getch() waits until an input is ready */
}

int MainMenu (void)
{
	/* Start-up menu.  Your wish is my command. :-) */

	int ch, key=0;

	Banner();
	attrset(COLOR_PAIR(4)|A_BOLD);
	mvprintw(10,28,"--==[ Main Menu ]==--");

	attrset(COLOR_PAIR(3)|A_BOLD);
	mvprintw(13,1,"[P]: Predict Satellite Passes");
	mvprintw(14,1,"[V]: Predict Visible Passes");
	mvprintw(15,1,"[S]: Solar Illumination Predictions");
	mvprintw(16,1,"[L]: Lunar Predictions");
	mvprintw(17,1,"[O]: Solar Predictions");
	mvprintw(18,1,"[T]: Single Satellite Tracking Mode");
	mvprintw(19,1,"[M]: Multi-Satellite Tracking Mode");

	mvprintw(13,40,"[I]: Program Information");
	mvprintw(14,40,"[G]: Edit Ground Station Information");
	mvprintw(15,40,"[D]: Display Satellite Orbital Data");
	mvprintw(16,40,"[U]: Update Sat Elements From File");
	mvprintw(17,40,"[E]: Manually Edit Orbital Elements");
	mvprintw(18,40,"[B]: Edit Transponder Database");
	mvprintw(19,40,"[Q]: Exit PREDICT");

	if (socket_flag)
	{
		attrset(COLOR_PAIR(4)|A_BOLD);
		mvprintw(22,33,"Server Mode");
	}

	refresh();

	if (xterm)
		fprintf(stderr,"\033]0;PREDICT: Version %s\007",PREDICT_VERSION);

	do
	{
		ch=getch();

		if (ch==KEY_MOUSE)
		{
			getmouse(&event);

			if (event.y>12 && event.y<20)
			{
				if (event.x>=1 && event.x<36)  /* First column */
				{
					if (event.bstate&BUTTON1_CLICKED || event.bstate&BUTTON1_DOUBLE_CLICKED || event.bstate&BUTTON1_PRESSED || event.bstate&BUTTON1_RELEASED)
					{
						if (event.y==13)
							key='p';

						if (event.y==14)
							key='v';

						if (event.y==15)
							key='s';

						if (event.y==16)
							key='l';

						if (event.y==17)
							key='o';

						if (event.y==18)
							key='t';

						if (event.y==19)
							key='m';
					}

					else if (event.y==18)
						key='T';
				}

				if (event.x>=40 && event.x<76)	/* Second column */
				{
					if (event.bstate&BUTTON1_CLICKED || event.bstate&BUTTON1_DOUBLE_CLICKED || event.bstate&BUTTON1_PRESSED || event.bstate&BUTTON1_RELEASED)
					{
						if (event.y==13)
							key='i';

						if (event.y==14)
							key='g';

						if (event.y==15)
							key='d';

						if (event.y==16)
							key='u';

						if (event.y==17)
							key='e';

						if (event.y==18)
							key='b';

						if (event.y==19)
							key='q';
					}
				}
			}
		}

		else if (ch<128)
			key=ch;

	} while (key!='p' && key!='v' && key!='s' && key!='l' && key!='o' && key!='t' && key!='T' && key!='m' && key!='i' && key!='g' && key!='d' && key!='u' && key!='e' && key!='b' && key!='q' && key!=27);

	if (key!='T')
		key=tolower(key);

	if (key==27)
		key='q';

	return key;
}

void ProgramInfo (void)
{
	int length=0, offset=0, y=0;

	struct stat file_info;
	time_t mod_time;

	Banner();
	attrset(COLOR_PAIR(3)|A_BOLD);

	length=22+strlen(qthfile);
	offset=(80-length)/2;

	if (offset<0)
		offset=0;

	mvprintw(11,offset,"PREDICT Version   : %s",PREDICT_VERSION);

	#if defined (__ANDROID__)
		printw(" (Android)");
	#endif

	#if defined (__MSDOS__)
		printw(" (DOS)");
	#endif

	mvprintw(12,offset,"QTH File Loaded   : %s",qthfile);
	mvprintw(13,offset,"KEP File Loaded   : %s",kepfile);

	if (stat(kepfile, &file_info)==0)
	{
		mod_time=file_info.st_mtime;
		mvprintw(14,offset,"KEP File Revised  : %s",asctime(gmtime(&mod_time)));
		mvprintw(14,offset+45,"UTC");
	}

	y=15;

	if (rotctld_socket!=-1)
	{
		mvprintw(y,offset,"AutoTracking      : Enabled");

		y++;

		if (rotctld_socket!=-1)
			mvprintw(y,offset,"rotctld           : %s:%s", rotctld_host, rotctld_port);

		y++;

		if (once_per_second)
		{
			mvprintw(y,offset,"Tracking Update   : Every second");
			y++;
		}
	}

	else
	{
		mvprintw(y,offset,"AutoTracking      : Not enabled");
		y++;
	}

	mvprintw(y,offset,"Running Mode      : ");

	y++;

	if (socket_flag)
		printw("Network server on port \"%s\"",netport);
	else
		printw("Standalone");

	mvprintw(y,offset,"Vocalizer         : ");

	if (SOUNDCARD)
		printw("Soundcard present");
	else
		printw("No ALSA available");

	refresh();
	attrset(COLOR_PAIR(4)|A_BOLD);
	AnyKey();
}

void NewUser(void)
{
	char result, destination[255]={0};

	Banner();
	attrset(COLOR_PAIR(3)|A_BOLD);

	/* Make "~/.predict" subdirectory */

	snprintf(temp,255,"%s/.predict",getenv("HOME"));
	mkdir(temp,0777);

	/* Copy default files into ~/.predict directory */

	snprintf(temp,255,"%sdefault/predict.kep",BUILD_DIR);
	copyFile(kepfile,temp);

	snprintf(temp,255,"%sdefault/predict.qth",BUILD_DIR);
	copyFile(qthfile,temp);

	snprintf(destination,255,"%s/.predict/groups.list",getenv("HOME"));
	snprintf(temp,255,"%sdefault/groups.list",BUILD_DIR);
	copyFile(destination,temp);

	/* "cp -r" default transponder directory into ~/.predict */

	snprintf(destination,255,"%s/.predict",getenv("HOME"));
	snprintf(temp,255,"%sdefault/xponders",BUILD_DIR);
	copyDir(destination,temp);

	/* Inform the human... */

	mvprintw(12,2,"WELCOME to PREDICT!  Since you are a new user to the program, default\n");
	printw("  orbital data and ground station location information was copied into\n");
	printw("  your home directory to get you started.\n\n");

	printw("  Now attempting to update the orbital data via the Internet... ");
	refresh();

	/* Read the .kep file we just copied into ~.predict. */

	ReadKEP();

	/* Force an update of the data */

	result=downloadKEP(1,1);

	if (result)
		printw("Done!\n\n");

	else
		printw(":-(\n  Please update your orbital database using Main Menu option [U] or [E].\n\n");

	refresh();

	printw("\n\n\t\tNow, let's edit your ground station information.");
	refresh();

	attrset(COLOR_PAIR(4)|A_BOLD);
	AnyKey();
}

void xponder_edit(void)
{
	int x, y, indx, ch=0, key=0, ans=0, before, after;
	char tmp[80], resave=0;
	double bw1=0.0, bw2=0.0, diff=0.0;


	/* Clear the need_to_save flags */

	for (x=0; x<24; x++)
		sat_xponder[x].need_to_save=0;

	indx=Select(); 
	
	while (indx!=-1)
	{
		if (sat_xponder[indx].squintflag==0)
		{
			sat_xponder[indx].alat=0.0;
			sat_xponder[indx].alon=0.0;
		}

		clear();
		x=0;

		strncpy(sat_xponder[indx].name,sat[indx].name,24);
		sat_xponder[indx].catnum=sat[indx].catnum;

		do
		{
			attrset(COLOR_PAIR(3)|A_BOLD);

			mvprintw(2,15,"* PREDICT Transponder Database Editing Utility *");

			attroff(COLOR_PAIR(3)|A_BOLD);
			attrset(COLOR_PAIR(4)|A_BOLD);

			mvprintw(5,5,"Name");
			mvprintw(5,40,"Catalog Number");

			attrset(COLOR_PAIR(2)|A_BOLD);

			mvprintw(5,9,": %s",sat_xponder[indx].name);
			mvprintw(5,54,": %u",sat_xponder[indx].catnum);

			attrset(COLOR_PAIR(4)|A_BOLD);

			if (sat_xponder[indx].transponders==0)
			{
				attroff(COLOR_PAIR(2)|A_BOLD);
				attrset(COLOR_PAIR(3)|A_BOLD);

				mvprintw(12,3,"Would you like to create a transponder entry for this satellite? [Y/N] ");
				curs_set(1);

			        do
			        {
					ans=toupper(getch());

				} while (ans!='Y' && ans!='N' && ans!=27);

				curs_set(0);

				if (ans=='Y')
				{
					sat_xponder[indx].transponders=1;
					database=1;

					move(12,3);
					clrtoeol();
					mvprintw(2,15,"* PREDICT Transponder Database Editing Utility *");

					attrset(COLOR_PAIR(4)|A_BOLD);
					refresh();
					continue;
				}

				if (ans=='N' || ans==27)
					break;
			}

			mvprintw(7,5,"Spacecraft Alat");
			mvprintw(7,40,"Alon");

			mvprintw(9,5,"Transponder #%-2d of %-2d",x+1, sat_xponder[indx].transponders);

			mvprintw(11,5,"Start of Uplink Passband   (MHz)");
			mvprintw(12,5,"End of Uplink Passband     (MHz)");

			mvprintw(14,5,"Start of Downlink Passband (MHz)");
			mvprintw(15,5,"End of Downlink Passband   (MHz)");

			mvprintw(17,5,"Day of Operation (Sun...Sat)");
			mvprintw(18,5,"Starting at Orbital Phase (0-255)");
			mvprintw(19,5,"Ending at Orbital Phase   (0-255)");

			attrset(COLOR_PAIR(2)|A_BOLD);

			mvprintw(7,20,": %.3f",sat_xponder[indx].alat);
			mvprintw(7,44,": %.3f",sat_xponder[indx].alon);

			mvprintw(9,26,": %-50s",sat_xponder[indx].transponder_name[x]);
			mvprintw(11,40,": %.4f    ",sat_xponder[indx].uplink_start[x]);
			mvprintw(12,40,": %.4f    ",sat_xponder[indx].uplink_end[x]);

			mvprintw(14,40,": %.4f    ",sat_xponder[indx].downlink_start[x]);
			mvprintw(15,40,": %.4f    ",sat_xponder[indx].downlink_end[x]);

			mvprintw(17,40,": %s",day[sat_xponder[indx].dayofweek[x]]);
			mvprintw(18,40,": %-3d",sat_xponder[indx].phase_start[x]);
			mvprintw(19,40,": %-3d",sat_xponder[indx].phase_end[x]);

			attroff(COLOR_PAIR(2)|A_BOLD);
			attrset(COLOR_PAIR(3)|A_BOLD);

			mvprintw(23,5,"<<=  [M]odify    [D]elete");

			if (x==0)
			{
				attroff(COLOR_PAIR(3)|A_BOLD);
				attrset(COLOR_PAIR(8));
			}

			mvprintw(23,34,"[B]ack");

			if (x==(sat_xponder[indx].transponders-1))
			{
				attroff(COLOR_PAIR(3)|A_BOLD);
				attrset(COLOR_PAIR(8));
			}
			else
				attrset(COLOR_PAIR(3)|A_BOLD);

			mvprintw(23,44,"[N]ext");

			if (x<10)
			{
				attroff(COLOR_PAIR(3)|A_BOLD);
				attrset(COLOR_PAIR(8));
			}
			else
				attrset(COLOR_PAIR(3)|A_BOLD);				

			if (sat_xponder[indx].transponders==10)
			{
				attroff(COLOR_PAIR(3)|A_BOLD);
				attrset(COLOR_PAIR(8));
			}
			else
				attrset(COLOR_PAIR(3)|A_BOLD);

			mvprintw(23,53,"[A]dd");

			attrset(COLOR_PAIR(3)|A_BOLD);

			mvprintw(23,62,"[Q]uit  =>>");

			refresh();

			ch=getch();

			if (ch==KEY_MOUSE)
			{
				getmouse(&event);

				if (event.y==23)
				{
					if (event.x>=10 && event.x<=17)
						key='M';

					if (event.x>=22 && event.x<=29)
						key='D';

					if (event.x>=34 && event.x<=39)
						key='B';

					if (event.x>=44 && event.x<=49)
						key='N';

					if (event.x>=53 && event.x<=57)
						key='A';

					if (event.x>=62 && event.x<=67)
						key='Q';
				}
			}

			else
				key=ch;

			if (key<128)
				key=toupper(key);

			if (key=='Q' || key==27)
				break;

			if (key==KEY_HOME || key==KEY_UP)
				x=0;

			if (key==KEY_END || key==KEY_DOWN)
				x=sat_xponder[indx].transponders-1;

			if ((key=='N' || key==KEY_RIGHT) && x<sat_xponder[indx].transponders-1)
				x++;

			if (key==' ')
			{
				if (x<(sat_xponder[indx].transponders-1))
					x++;
				else
					x=0;
			}

			if ((key=='B' || key==KEY_LEFT) && x!=0)
				x--;

			if (key=='A')
			{
				if (sat_xponder[indx].transponders<10)
				{
					sat_xponder[indx].transponders++;
					y=sat_xponder[indx].transponders-1;

					if (x<y)
						x++;

					/* Initialize the new Add position */

					sat_xponder[indx].transponder_name[y][0]=0;
					sat_xponder[indx].uplink_start[y]=0.0;
					sat_xponder[indx].uplink_end[y]=0.0;
					sat_xponder[indx].downlink_start[y]=0.0;
					sat_xponder[indx].downlink_end[y]=0.0;
					sat_xponder[indx].dayofweek[y]=0;
					sat_xponder[indx].phase_start[y]=0;
					sat_xponder[indx].phase_end[y]=0;

					ungetch('M');	/* Next, modify the added entry */

					resave=1;
					sat_xponder[indx].need_to_save=1;
				}
			}

			if (key=='M')
			{
				attroff(COLOR_PAIR(3)|A_BOLD);
				attrset(COLOR_PAIR(2)|A_BOLD);

				snprintf(tmp,7,"%.3f",sat_xponder[indx].alat);

				mvprintw(21,5,"Enter the Bahn Latitude for this satellite.  Enter 0.000 if unsure.");

				before=strlen(tmp);

				if (LineEdit(22,7,tmp))
				{
					sat_xponder[indx].alat=atof(tmp);
					resave=1;
					sat_xponder[indx].need_to_save=1;
				}

				after=strlen(tmp);

				mvprintw(7,20,": %.3f",sat_xponder[indx].alat);

				for (y=before; y<after; y++)
					addch(' ');

				/****/

				snprintf(tmp,7,"%.3f",sat_xponder[indx].alon);

				move(21,5);
				clrtoeol();
				mvprintw(21,5,"Enter the Bahn Longitude for this satellite.  Enter 0.000 if unsure.");

				before=strlen(tmp);
				
				if (LineEdit(46,7,tmp))
				{
					sat_xponder[indx].alon=atof(tmp);
					resave=1;
					sat_xponder[indx].need_to_save=1;
				}

				after=strlen(tmp);

				mvprintw(7,44,": %.3f",sat_xponder[indx].alon);

				for (y=before; y<after; y++)
					addch(' ');

				if (sat_xponder[indx].alat!=0.0 || sat_xponder[indx].alon!=0.0)
					sat_xponder[indx].squintflag=1;

				/****/

				snprintf(tmp,51,"%s",sat_xponder[indx].transponder_name[x]);

				move(21,5);
				clrtoeol();
				mvprintw(21,5,"Enter the name or description of this transponder.");

				before=strlen(tmp);

				if (LineEdit(28,9,tmp))
				{
					strncpy2(sat_xponder[indx].transponder_name[x],tmp,50);
					resave=1;
					sat_xponder[indx].need_to_save=1;
				}

				after=strlen(tmp);

				mvprintw(9,26,": %-50s",sat_xponder[indx].transponder_name[x]);

				for (y=before; y<after; y++)
					addch(' ');

				/****/

				snprintf(tmp,10,"%.4f",sat_xponder[indx].uplink_start[x]);

				move(21,5);
				clrtoeol();
				mvprintw(21,5,"Enter 0.000 if there is no uplink.");

				before=strlen(tmp);

				if (LineEdit(42,11,tmp))
				{
					sat_xponder[indx].uplink_start[x]=atof(tmp);
					resave=1;
					sat_xponder[indx].need_to_save=1;
				}

				after=strlen(tmp);

				mvprintw(11,40,": %.4f    ",sat_xponder[indx].uplink_start[x]);

				for (y=before; y<after; y++)
					addch(' ');

				if (sat_xponder[indx].uplink_start[x]==0.0)
					sat_xponder[indx].uplink_end[x]=sat_xponder[indx].uplink_start[x];

				/****/

				snprintf(tmp,10,"%.4f",sat_xponder[indx].uplink_end[x]);

				if (sat_xponder[indx].uplink_start[x]!=0.0)
				{
					move(21,5);
					clrtoeol();
					mvprintw(21,5,"Enter 0.000 if this is a single uplink frequency.");

					before=strlen(tmp);

					if (LineEdit(42,12,tmp))
					{
						sat_xponder[indx].uplink_end[x]=atof(tmp);
						resave=1;
						sat_xponder[indx].need_to_save=1;
					}
				}

				if (sat_xponder[indx].uplink_start[x]!=0.0 && sat_xponder[indx].uplink_end[x]==0.0)
					sat_xponder[indx].uplink_end[x]=sat_xponder[indx].uplink_start[x];

				after=strlen(tmp);

				mvprintw(12,40,": %.4f    ",sat_xponder[indx].uplink_end[x]);

				for (y=before; y<after; y++)
					addch(' ');

				bw1=fabs(sat_xponder[indx].uplink_start[x]-sat_xponder[indx].uplink_end[x]);

				/****/

				snprintf(tmp,10,"%.4f",sat_xponder[indx].downlink_start[x]);

				move(21,5);
				clrtoeol();
				mvprintw(21,5,"Enter 0.000 if there is no downlink or beacon.");

				before=strlen(tmp);

				if (LineEdit(42,14,tmp))
				{
					sat_xponder[indx].downlink_start[x]=atof(tmp);
					resave=1;
					sat_xponder[indx].need_to_save=1;
				}

				mvprintw(14,40,": %.4f    ",sat_xponder[indx].downlink_start[x]);

				after=strlen(tmp);

				for (y=before; y<after; y++)
					addch(' ');

				if (sat_xponder[indx].downlink_start[x]==0.0)
					sat_xponder[indx].downlink_end[x]=sat_xponder[indx].downlink_start[x];

				/****/

				snprintf(tmp,10,"%.4f",sat_xponder[indx].downlink_end[x]);

				if (sat_xponder[indx].downlink_start[x]!=0.0)
				{

					move(21,5);
					clrtoeol();
					mvprintw(21,5,"Enter 0.000 if this is a single frequency downlink or beacon.");

					before=strlen(tmp);

					if (LineEdit(42,15,tmp))
					{
						sat_xponder[indx].downlink_end[x]=atof(tmp);
						resave=1;
						sat_xponder[indx].need_to_save=1;
					}
				}

				if (sat_xponder[indx].downlink_start[x]!=0.0 && sat_xponder[indx].downlink_end[x]==0.0)
				{
					sat_xponder[indx].downlink_end[x]=sat_xponder[indx].downlink_start[x];
					resave=1;
					sat_xponder[indx].need_to_save=1;
				}

				mvprintw(15,40,": %.4f    ",sat_xponder[indx].downlink_end[x]);

				after=strlen(tmp);

				for (y=before; y<after; y++)
					addch(' ');

				bw2=fabs(sat_xponder[indx].downlink_start[x]-sat_xponder[indx].downlink_end[x]);

				/****/

				snprintf(tmp,4,"%s",day[sat_xponder[indx].dayofweek[x]]);

				move(21,5);
				clrtoeol();
				mvprintw(21,5,"The day of the week this transponder or downlink is active.");

				before=strlen(tmp);

				/** Find the day of the week and convert it to a single digit **/

				if (LineEdit(42,17,tmp))
				{
					for (y=0; y<8; y++)
					{
						if (strncasecmp(day[y],tmp,3)==0)
							sat_xponder[indx].dayofweek[x]=y;
					}

					resave=1;
					sat_xponder[indx].need_to_save=1;
				}

				mvprintw(17,40,": %s",day[sat_xponder[indx].dayofweek[x]]);

				after=strlen(tmp);

				for (y=before; y<after; y++)
					addch(' ');

				/****/

				snprintf(tmp,4,"%d",sat_xponder[indx].phase_start[x]);

				move(21,5);
				clrtoeol();
				mvprintw(21,5,"Orbital phase when this transponder turns ON.  Enter 0 if unsure.");

				before=strlen(tmp);

				if (LineEdit(42,18,tmp))
				{
					y=atol(tmp);

					if (y>255 || y<0)
						y=0;

					sat_xponder[indx].phase_start[x]=y;

					resave=1;
					sat_xponder[indx].need_to_save=1;
				}

				mvprintw(18,40,": %d",sat_xponder[indx].phase_start[x]);

				after=strlen(tmp);

				for (y=before; y<after; y++)
					addch(' ');

				/****/
 
				snprintf(tmp,4,"%d",sat_xponder[indx].phase_end[x]);

				move(21,5);
				clrtoeol();
				mvprintw(21,5,"Orbital phase when this transponder turns OFF.  Enter 0 if unsure.");
				
				before=strlen(tmp);

				if (LineEdit(42,19,tmp))
				{
					y=atol(tmp);

					if (y>255 || y<0)
						y=0;

					sat_xponder[indx].phase_end[x]=y;

					resave=1;
					sat_xponder[indx].need_to_save=1;
				}

				move(21,5);
				clrtoeol();

				diff=fabs(bw1-bw2);

				if ((diff>1e-5)&&(sat_xponder[indx].uplink_start[x]!=0.0 && sat_xponder[indx].downlink_start[x]!=0.0))
					mvprintw(21,5,"Transponder bandwidth discrepancy!  Fix the uplinks and/or downlinks.");
				else
				{
					if (resave)
						mvprintw(21,20,"Good!  Now choose one of the options below.");
					else
						mvprintw(21,16,"No changes made!  Choose one of the options below.");

				}

				mvprintw(19,40,": %d",sat_xponder[indx].phase_end[x]);

				after=strlen(tmp);

				for (y=before; y<after; y++)
					addch(' ');

				attroff(COLOR_PAIR(2)|A_BOLD);
                        	attrset(COLOR_PAIR(3)|A_BOLD);
			}

			if (key=='D')
			{
				attroff(COLOR_PAIR(3)|A_BOLD);
				attrset(COLOR_PAIR(2)|A_BOLD);

				mvprintw(21,8,"Are you sure you want to delete this transponder entry? [Y/N] ");
                                curs_set(1);

                                do
                                {
                                        ans=toupper(getch());

                                } while (ans!='Y' && ans!='N' && ans!=27);

                                curs_set(0);

				move(21,5);
				clrtoeol();

				if (ans=='Y')
				{
					if (sat_xponder[indx].transponders>0)
					{
						/* Move each element in the transponder description down one position. */

						for (y=x; y<9; y++)
						{
							strncpy(sat_xponder[indx].transponder_name[y],sat_xponder[indx].transponder_name[y+1],50);
							sat_xponder[indx].transponder_name[y][49]=0;
							sat_xponder[indx].uplink_start[y]=sat_xponder[indx].uplink_start[y+1];
							sat_xponder[indx].uplink_end[y]=sat_xponder[indx].uplink_end[y+1];
							sat_xponder[indx].downlink_start[y]=sat_xponder[indx].downlink_start[y+1];
							sat_xponder[indx].downlink_end[y]=sat_xponder[indx].downlink_end[y+1];
							sat_xponder[indx].dayofweek[y]=sat_xponder[indx].dayofweek[y+1];
							sat_xponder[indx].phase_start[y]=sat_xponder[indx].phase_start[y+1];
							sat_xponder[indx].phase_end[y]=sat_xponder[indx].phase_end[y+1];
						}

						/* Clear out the attic. */

						for (y=sat_xponder[indx].transponders; y<10; y++)
						{
							sat_xponder[indx].transponder_name[y][0]=0;
							sat_xponder[indx].uplink_start[y]=0.0;
							sat_xponder[indx].uplink_end[y]=0.0;
							sat_xponder[indx].downlink_start[y]=0.0;
							sat_xponder[indx].downlink_end[y]=0.0;
							sat_xponder[indx].dayofweek[y]=0;
							sat_xponder[indx].phase_start[y]=0;
							sat_xponder[indx].phase_end[y]=0;
						}

						if (x!=0)
							x--;

						sat_xponder[indx].transponders--;
						sat_xponder[indx].need_to_save=1;
						resave=1;

						if (sat_xponder[indx].transponders==0)
							break;
					}
                                }

                                if (x==(sat_xponder[indx].transponders-2))
                                        x++;
			}

			refresh();

		} while ((key!='Q' && key!=27) || (x<sat_xponder[indx].transponders));

		indx=Select();
	}

	if (resave)
		save_xponders();
}

int QuickFind (char *string, char *outputfile)
{
	int x, y, z, step=1;
	long start, now, end, count;
	double doppler100=0.0;
	char satname[50], startstr[20], endstr[20];
	time_t t;
	FILE *fd;

	if (outputfile[0])
		fd=fopen(outputfile,"w");
	else
		fd=stdout;

	startstr[0]=0;
	endstr[0]=0;

	ReadDataFiles();

	for (x=0; x<48 && string[x]!=0 && string[x]!='\n'; x++)
		satname[x]=string[x];

	satname[x]=0;
	x++;

	for (y=0; string[x+y]!=0 && string[x+y]!='\n'; y++)
		startstr[y]=string[x+y];

	startstr[y]=0;
	y++;

	for (z=0; string[x+y+z]!=0 && string[x+y+z]!='\n'; z++)
		endstr[z]=string[x+y+z];

	endstr[z]=0;
 
	/* Do a simple search for the matching satellite name */

	for (z=0; z<24; z++)
	{
		if ((strcmp(sat[z].name,satname)==0) || (atol(satname)==sat[z].catnum))
		{
			start=atol(startstr);

			if (endstr[strlen(endstr)-1]=='m')
			{
				step=60;
				endstr[strlen(endstr)-1]=0;
			}
			
			if (endstr[0]=='+')
				end=start+((long)step)*atol(endstr);
			else
				end=atol(endstr);

			indx=z;

			t=time(NULL);
			now=(long)t;

			if (start==0)
				start=now;

			if (startstr[0]=='+')
			{
				start=now;

				if (startstr[strlen(startstr)-1]=='m')
				{
					step=60;
					startstr[strlen(startstr)-1]=0;
				}

				end=start+((long)step)*atol(startstr);

				/* Prevent a list greater than
				   24 hours from being produced */

				if ((end-start)>86400)
				{
					start=now;
					end=now-1;
				}
			}

			if ((start>=now-31557600) && (start<=now+31557600) && end==0)
			{
				/* Start must be one year from now */
				/* Display a single position */

				daynum=(start/86400.0);

				PreCalc(indx);
				Calc();

				if (Decayed(indx,daynum)==0)
				{
					if (iel<0)
						fprintf(fd,"%ld %s %4d %4d %4d %4d %4d %6ld %6ld %c\n",start,Daynum2String(daynum),iel,iaz,ma256,isplat,isplong,irk,rv,findsun);
					else
					{
						doppler100=-100.0e06*((sat_range_rate*1000.0)/299792458.0);
						fprintf(fd,"%ld %s %4d %4d %4d %4d %4d %6ld %6ld %c %f\n",start,Daynum2String(daynum),iel,iaz,ma256,isplat,isplong,irk,rv,findsun,doppler100);
					}
				}
				break;
			}

			else
			{
				/* Display a whole list */

				for (count=start; count<=end; count+=step)
				{
					// daynum=((count/86400.0)-3651.0);
					daynum=(count/86400.0);

					PreCalc(indx);
					Calc();

					if (Decayed(indx,daynum)==0)
						fprintf(fd,"%ld %s %4d %4d %4d %4d %4d %6ld %6ld %c\n",count,Daynum2String(daynum),iel,iaz,ma256,isplat,isplong,irk,rv,findsun);
				}

				break;
			}
		}
	}

	if (outputfile[0])
		fclose(fd);

	return 0;
}

int QuickPredict (char *string, char *outputfile)
{
	int x, y, z, lastel=0;
	long start, now;
	double doppler100=0.0;
	char satname[50], startstr[20];
	time_t t;

	FILE *fd;

	if (outputfile[0])
		fd=fopen(outputfile,"w");
	else
		fd=stdout;

	startstr[0]=0;

	ReadDataFiles();

	for (x=0; x<48 && string[x]!=0 && string[x]!='\n'; x++)
		satname[x]=string[x];

	satname[x]=0;
	x++;

	for (y=0; string[x+y]!=0 && string[x+y]!='\n'; y++)
		startstr[y]=string[x+y];

	startstr[y]=0;
	y++;

	/* Do a simple search for the matching satellite name */

	for (z=0; z<24; z++)
	{
		if ((strcmp(sat[z].name,satname)==0) || (atol(satname)==sat[z].catnum))
		{
			start=atol(startstr);
			indx=z;

			t=time(NULL);
			now=(long)t;

			if (start==0)
				start=now;

			if ((start>=now-31557600) && (start<=now+31557600))
			{
				/* Start must within one year of now */

				// daynum=((start/86400.0)-3651.0);
				daynum=(start/86400.0);

				PreCalc(indx);
				Calc();

				if (AosHappens(indx) && Geostationary(indx)==0 && Decayed(indx,daynum)==0)
				{
					/* Make Predictions */

					daynum=FindAOS();

					/* Display the pass */

					while (iel>=0)
					{
						doppler100=-100.0e06*((sat_range_rate*1000.0)/299792458.0);
						// fprintf(fd,"%.0f %s %4d %4d %4d %4d %4d %6ld %6ld %c %f\n",floor(86400.0*(3651.0+daynum)),Daynum2String(daynum),iel,iaz,ma256,isplat,isplong,irk,rv,findsun,doppler100);

						fprintf(fd,"%.0f %s %4d %4d %4d %4d %4d %6ld %6ld %c %f\n",floor(86400.0*daynum),Daynum2String(daynum),iel,iaz,ma256,isplat,isplong,irk,rv,findsun,doppler100);

						lastel=iel;
						daynum+=cos((sat_ele-1.0)*deg2rad)*sqrt(sat_alt)/25000.0;
						Calc();
					}

					if (lastel!=0)
					{
						daynum=FindLOS();
						Calc();
						doppler100=-100.0e06*((sat_range_rate*1000.0)/299792458.0);

						// fprintf(fd,"%.0f %s %4d %4d %4d %4d %4d %6ld %6ld %c %f\n",floor(86400.0*(3651.0+daynum)),Daynum2String(daynum),iel,iaz,ma256,isplat,isplong,irk,rv,findsun,doppler100);

						fprintf(fd,"%.0f %s %4d %4d %4d %4d %4d %6ld %6ld %c %f\n",floor(86400.0*daynum),Daynum2String(daynum),iel,iaz,ma256,isplat,isplong,irk,rv,findsun,doppler100);
					}
				}
				break;
			}
		}
	}

	if (outputfile[0])
		fclose(fd);

	return 0;
}

int QuickDoppler100 (char *string, char *outputfile)
{
	/* Do a quick predict of the doppler for non-geo sattelites.
	   Return UTC epoch seconds, UTC time and doppler normalized
	   to 100 MHz for every 5 seconds of satellite-pass as a CSV. */

	int x, y, z, lastel=0;
	long start, now;
	double doppler100;
	char satname[50], startstr[20];
	time_t t;

	FILE *fd;

	if (outputfile[0])
		fd=fopen(outputfile,"w");
	else
		fd=stdout;

	startstr[0]=0;

	ReadDataFiles();

	for (x=0; x<48 && string[x]!=0 && string[x]!='\n'; x++)
		satname[x]=string[x];

	satname[x]=0;
	x++;

	for (y=0; string[x+y]!=0 && string[x+y]!='\n'; y++)
		startstr[y]=string[x+y];

	startstr[y]=0;
	y++;

	/* Do a simple search for the matching satellite name */

	for (z=0; z<24; z++)
	{
		if ((strcmp(sat[z].name,satname)==0) || (atol(satname)==sat[z].catnum))
		{
			start=atol(startstr);
			indx=z;

			t=time(NULL);
			now=(long)t;

			if (start==0)
				start=now;

			if ((start>=now-31557600) && (start<=now+31557600))
			{
				/* Start must within one year of now */

				// daynum=((start/86400.0)-3651.0);
				daynum=(start/86400.0);

				PreCalc(indx);
				Calc();

				if (AosHappens(indx) && Geostationary(indx)==0 && Decayed(indx,daynum)==0)
				{
					/* Make Predictions */

					daynum=FindAOS();

					/* Display the pass */

					while (iel>=0)
					{
						doppler100=-100.0e06*((sat_range_rate*1000.0)/299792458.0);
						// fprintf(fd,"%.0f,%s,%f\n",floor(86400.0*(3651.0+daynum)),Daynum2String(daynum),doppler100);

						fprintf(fd,"%.0f,%s,%f\n",floor(86400.0*daynum),Daynum2String(daynum),doppler100);
						lastel=iel;
						daynum+=cos((sat_ele-1.0)*deg2rad)*sqrt(sat_alt)/500000.0;
						Calc();
					}

					if (lastel!=0)
					{
						doppler100=-100.0e06*((sat_range_rate*1000.0)/299792458.0);
						daynum=FindLOS();
						Calc();

						// fprintf(fd,"%.0f,%s,%f\n",floor(86400.0*(3651.0+daynum)),Daynum2String(daynum),doppler100);
						fprintf(fd,"%.0f,%s,%f\n",floor(86400.0*daynum),Daynum2String(daynum),doppler100);
					}
				}

				break;
			}
		}
	}

	if (outputfile[0])
		fclose(fd);

	return 0;
}

void OScheck(void)
{
	/* Check to see if termux-wake-lock is available */
	/* and set the global android flag accordingly. */

	FILE *test=NULL;

	test=fopen("/data/data/com.termux/files/usr/bin/termux-wake-lock", "r");

	if (test!=NULL)
	{
		android=1;
		fclose(test);
	}
	else
		android=0;
}

void migration_check(void)
{
	char tle_file[255], kep_file[255];

	FILE *tle=NULL, *kep=NULL;

	snprintf(tle_file,255,"%s/.predict/predict.tle%c",getenv("HOME"),0);
	snprintf(kep_file,255,"%s/.predict/predict.kep%c",getenv("HOME"),0);

	kep=fopen(kep_file,"r");
	tle=fopen(tle_file,"r");

	if (kep==NULL && tle!=NULL)
	{
		/* Migrate the existing TLE file to the new KEP file and format. */

		fclose(tle);
		tle_migration();
	}

	if (kep!=NULL)
		fclose(kep);

	xponder_migration();
}

int main(int argc, char *argv[])
{
	int x, y, z, key=0;
	char updatefile[80], quickfind=0, quickpredict=0,
	     quickstring[40], outputfile[42], quickdoppler100=0,
	     kep_cli[80], qth_cli[80], interactive=0, *env=NULL;
	struct addrinfo hints, *servinfo, *servinfop;
	pthread_t thread;

	/* Set up translation table for computing TLE checksums */

	for (x=0; x<=255; val[x]=0, x++);
	for (x='0'; x<='9'; val[x]=x-'0', x++);

	val['-']=1;

	updatefile[0]=0;
	outputfile[0]=0;
	temp[0]=0;
	kep_cli[0]=0;
	qth_cli[0]=0;
	netport[0]=0;
	once_per_second=0;
	database=0;
		
	y=argc-1;
	rotctld_socket=-1;

	memset(&hints, 0, sizeof hints);
	hints.ai_family=AF_UNSPEC;
	hints.ai_socktype=SOCK_STREAM;

	strncpy2(rotctld_port,"4533",4);

	/* Make sure entire "quickstring" array is initialized before use */

	for (x=0; x<40; quickstring[x]=0, x++);

	/* Scan command-line arguments */

	for (x=1; x<=y; x++)
	{
		if (strcmp(argv[x],"-f")==0)
		{
			quickfind=1;
			z=x+1;
			while (z<=y && argv[z][0] && argv[z][0]!='-')
			{
				if ((strlen(quickstring)+strlen(argv[z]))<37)
				{
					strncat(quickstring,argv[z],15);
					strcat(quickstring,"\n");
					z++;
				}
			}
			z--;
		}

		if (strcmp(argv[x],"-p")==0)
		{
			quickpredict=1;
			z=x+1;

			while (z<=y && argv[z][0] && argv[z][0]!='-')
			{
				if ((strlen(quickstring)+strlen(argv[z]))<37)
				{
					strncat(quickstring,argv[z],15);
					strcat(quickstring,"\n");
					z++;
				}
			}
			z--;
		}

		if (strcmp(argv[x],"-dp")==0)
		{
			quickdoppler100=1;
			z=x+1;

			while (z<=y && argv[z][0] && argv[z][0]!='-')
			{
				if ((strlen(quickstring)+strlen(argv[z]))<37)
				{
					strncat(quickstring,argv[z],15);
					strcat(quickstring,"\n");
					z++;
				}
			}
			z--;
		}

		if (strcmp(argv[x],"-u")==0)
		{
			z=x+1;
			while (z<=y && argv[z][0] && argv[z][0]!='-')
			{
				if ((strlen(updatefile)+strlen(argv[z]))<75)
				{
					strncat(updatefile,argv[z],75);
					strcat(updatefile,"\n");
					z++;
				}
			}
			z--;	
		}

		if (strcmp(argv[x],"-t")==0)
		{
			z=x+1;
			if (z<=y && argv[z][0] && argv[z][0]!='-')
				strncpy2(kep_cli,argv[z],78);
		}

		if (strcmp(argv[x],"-q")==0)
		{
			z=x+1;
			if (z<=y && argv[z][0] && argv[z][0]!='-')
				strncpy2(qth_cli,argv[z],78);
		}

		if (strcmp(argv[x],"-o")==0)
		{
			z=x+1;
			if (z<=y && argv[z][0] && argv[z][0]!='-')
				strncpy2(outputfile,argv[z],40);
		}

		if (strcmp(argv[x],"-port")==0)
		{
			z=x+1;
			if (z<=y && argv[z][0] && argv[z][0]!='-')
				strncpy2(netport,argv[z],5);
		}

		if (strcmp(argv[x],"-host")==0)
		{
			z=x+1;
			if (z<=y && argv[z][0] && argv[z][0]!='-')
				strncpy2(rotctld_host,argv[z],sizeof(rotctld_host)-1);

			rotctld_host[sizeof(rotctld_host)-1]=0;
		}

		if (strcmp(argv[x],"-sync")==0)
			force=1;

		if (strcmp(argv[x],"-s")==0)
			socket_flag=1;

		if (strcmp(argv[x],"-north")==0) /* Default */
			io_lat='N';

		if (strcmp(argv[x],"-south")==0)
			io_lat='S';

		if (strcmp(argv[x],"-west")==0)  /* Default */
			io_lon='W';

		if (strcmp(argv[x],"-east")==0)
			io_lon='E';
	}

	/* We're done scanning command-line arguments */

	/* If no command-line (-t, -q, or -d) arguments have been passed
	   to PREDICT, create qth, kep, and database filenames based on
	   the default ($HOME) directory. */

	env=getenv("HOME");

	if (qth_cli[0]==0)
		sprintf(qthfile,"%s/.predict/predict.qth",env);
	else
		sprintf(qthfile,"%s",qth_cli);

	if (kep_cli[0]==0)
		sprintf(kepfile,"%s/.predict/predict.kep",env);
	else
		sprintf(kepfile,"%s",kep_cli);

	/* Test for interactive/non-interactive mode of operation
	   based on command-line arguments given to PREDICT. */

	if (updatefile[0] || quickfind || quickpredict || quickdoppler100)
		interactive=0;
	else
		interactive=1;

	migration_check(); /* NEW for PREDICT v3.x.x */
	x=ReadDataFiles();

	if (x>1)  /* KEP file was loaded successfully */
	{
		if (updatefile[0])  /* -u was passed to PREDICT */
		{
			y=0;
			z=0;
			temp[0]=0;

			while (updatefile[y]!=0)
			{
				while (updatefile[y]!='\n' && updatefile[y]!=0 && y<79)
				{
					temp[z]=updatefile[y];

					if (z<79)
						z++;
					y++;
				}

				temp[z]=0;

				if (temp[0])
				{
					autoUpdate(temp);
					temp[0]=0;
					z=0;
					y++;
				}
			}

			exit(0);
		}

		else
			/* Refresh the Keplerian Elements */
			/* either if needed, or if force==1. */
 
			downloadKEP(0,force);
	}

	if (x==3)  /* Both TLE and QTH files were loaded successfully */
	{
		if (quickfind)  /* -f was passed to PREDICT */
			exit(QuickFind(quickstring,outputfile));

		if (quickpredict)  /* -p was passed to PREDICT */
			exit(QuickPredict(quickstring,outputfile));

		if (quickdoppler100)  /* -dp was passed to PREDICT */
			exit(QuickDoppler100(quickstring,outputfile));
	}

	else
	{
		if (kep_cli[0] || qth_cli[0])
		{
			/* "Houston, we have a problem..." */

			printf("\n%c",7);

			if (x^1)
				printf("*** ERROR!  Your QTH file \"%s\" could not be loaded!\n",qthfile);

			if (x^2)
				printf("*** ERROR!  Your KEP file \"%s\" could not be loaded!\n",kepfile);

			printf("\n");

			exit(-1);
		}
	}

	/* Create socket and connect to rotctld. */

	if (rotctld_host[0]!=0)
	{
		if (getaddrinfo(rotctld_host, rotctld_port, &hints, &servinfo))
		{
			bailout("getaddrinfo error");
			exit(-1);
		}

		for (servinfop=servinfo; servinfop!=NULL; servinfop=servinfop->ai_next)
		{
			if ((rotctld_socket=socket(servinfop->ai_family, servinfop->ai_socktype,servinfop->ai_protocol))==-1)
				continue;

			if (connect(rotctld_socket, servinfop->ai_addr, servinfop->ai_addrlen)==-1)
			{
				close(rotctld_socket);
				continue;
			}

			break;
		}

		if (servinfop==NULL)
		{
			bailout("Start \"rotctld\" first before running PREDICT");
			exit(-1);
		}

		freeaddrinfo(servinfo);

		/* TrackDataNet() will wait for confirmation of a command before sending
		   the next so we bootstrap this by asking for the current position. */

		send(rotctld_socket, "p\n", 2, 0);
	}

	if (interactive)
	{
		/* We're in interactive mode.  Prepare the screen. */
		/* Are we running under an xterm or equivalent? */

		OScheck();

		env=getenv("TERM");

		if (env!=NULL && strncmp(env,"xterm",5)==0)
			xterm=1;
		else
			xterm=0; 

		/* Start ncurses */

		initscr();
		start_color();
		cbreak();		/* No keyboard buffering */
		noecho();		/* No echo */
		set_escdelay(50);	/* Reduce [ESC] response to 50 ms */
		keypad(stdscr,TRUE);	/* Prevents function key mayhem */
		curs_set(0);		/* Cursor OFF */
		nodelay(stdscr,FALSE);	/* getch() waits until there's a keystroke */

		mousemask(ALL_MOUSE_EVENTS | REPORT_MOUSE_POSITION, &bstate);

		init_pair(1,COLOR_WHITE,COLOR_BLACK);
		init_pair(2,COLOR_WHITE,COLOR_BLUE);
		init_pair(3,COLOR_YELLOW,COLOR_BLUE);
		init_pair(4,COLOR_CYAN,COLOR_BLUE);
		init_pair(5,COLOR_WHITE,COLOR_RED);
		init_pair(6,COLOR_GREEN,COLOR_BLUE);
		init_pair(7,COLOR_CYAN,COLOR_RED);
		init_pair(8,COLOR_BLACK,COLOR_BLUE);
		init_pair(9,COLOR_RED,COLOR_BLUE);

		if (android)
			init_color(COLOR_BLUE, 0, 0, 500);  /* Darken the blue background */

		if (x<3)
		{
			/* A problem occurred reading the
			   default QTH and TLE files, and
			   no -t or -q options were
			   provided on the command-line. */

			NewUser();
			x=ReadDataFiles();
			QthEdit();
		}
	}

	if (x==3)
	{
		/* Socket activated here.  Remember that
		   the socket data is updated only when
		   running in the real-time tracking modes. */

		if (socket_flag)
		{
			pthread_create(&thread,NULL,(void *)socket_server,(void *)argv[0]);
			bkgdset(COLOR_PAIR(3));
			MultiTrack();
		}

		do
		{
			key=MainMenu();

			switch (key)
			{
				case 'p':
				case 'v':
					Print("",0);
					PrintVisible("");
					indx=Select();

					if (indx!=-1 && sat[indx].meanmo!=0.0 && Decayed(indx,0.0)==0)
						Predict(key);

					break;

				case 'l':
					Print("",0);
					PredictMoon();
					break;

				case 'o':
					Print("",0);
					PredictSun();
					break;

				case 'u':
					autoUpdate("");
					break;

				case 'e':
					KepEdit();
					break;

				case 'd':
					ShowOrbitData();
					break;

				case 'g':
					QthEdit();
					break;

				case 't':
				case 'T':
					indx=Select();

					if (indx!=-1 && sat[indx].meanmo!=0.0 && Decayed(indx,0.0)==0)
						SingleTrack(indx,key);

					break;

				case 'm':
					do
					{
						indx=MultiTrack();

						if (indx!=-1 && sat[indx].meanmo!=0.0 && Decayed(indx,0.0)==0)
							SingleTrack(indx,'t');

					} while (indx!=-1);

					break;

				case 'i':
					ProgramInfo();
					break;

				case 'b':
					xponder_edit();
					break;

				case 's':
					indx=Select();
					if (indx!=-1 && sat[indx].meanmo!=0.0 && Decayed(indx,0.0)==0)
					{
						Print("",0);
						Illumination();
					}
					break;
			}

		} while (key!='q' && key!=27);

		if (rotctld_socket!=-1)
		{
			send(rotctld_socket, "q\n", 2, 0);
			close(rotctld_socket);
		}

		curs_set(1);	
		bkgdset(COLOR_PAIR(1));
		clear();
		refresh();
		endwin();
	}

	exit(0);
}

