// sattool - visual satellite tracking and prediction tool.
// Copyright 2000 Tom Rothamel <tom-idbg@onegeek.org>
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.  

#include "sattool.h"
#include <curses.h>
#include <unistd.h>
#include <vector.h>
#include <stdarg.h>
#include <stdlib.h>

int timeoffset = 0;
int lockoffset = 0;

int timelock = 0;
int display = 0;

static List *l;
static Thing *t;
static Object *site;
static Sat *s;
static Object *obj;
static Pass *p;

struct Keymap {
	int c;
	int(*func)();
};

static char statusbuf[80];

// Display the status line.
void live_status() {
	mvprintw(LINES-1, 0, "%s", statusbuf);
	move(LINES-1, COLS-1);

	refresh();
}

// Change the status line.
static void status(char *fmt, ...) {
	va_list ap;

	va_start(ap, fmt);
	vsnprintf(statusbuf, 80, fmt, ap);
	va_end(ap);

	live_status();
}

// Display the header (site and time info.)
void live_header(double t) {
	char buf[80];

	erase();
	snprintf(buf, 79, "-- Sattool Live Tracking Display - %s --------------------------------------------\n",
		site->name);
	mvprintw(0, 0, buf);
	strfsgptime(buf, 40, "%Y%m%d %H:%M:%S %Z", t);
	mvprintw(2, 40, "Local Time: %s", buf);
	mvprintw(3, 40, "Epoch Time: %.5f", t);

	if (timeoffset || lockoffset) {
		mvprintw(4, 39, "Time Offset: %d/%d\n", timeoffset,
			 lockoffset);
	}
}

// Display the satellite name and designation.
void live_name(double t) {

	if (!obj) return;
	
	move(3, 0);
	printw("         Name: %s", obj->name);

	if (!s) return;
	move(4, 0);
        printw("International: %s", s->desig);
	move(5, 0);
	printw(" Norad Number: %05d", s->norad); 
}
	
// Display the eci vectors.
void live_ecivec(double t) {
	Position *p;

	if (!obj) return;

	p = obj->calcpos(t, NULL);

	move(7, 0);
	
	printw("   X: %11.3f  Y: %11.3f  Z: %11.3f\n",
		 p->x[0], p->x[1], p->x[2]);
	printw("  dX: %11.3f dY: %11.3f dZ: %11.3f\n",
		 p->dx[0], p->dx[1], p->dx[2]);
	delete p;
}

// Display the look angles, range, and lat/lon info.
void live_looklla(double t) {
	Encounter *e;
	LLA *lla;

	if (!obj) return;
	
	e = new Encounter(obj, site, t);
	e->calcmore();
	lla = new LLA(e->objp);

	move(10, 0);
	
	printw("   Azimuth: %8.3f %s    Latitude: %9.3f %s\n",
	       e->azimuth, CompassDir(e->azimuth), fabs(lla->lat),
	       (lla->lat > 0) ? "N" : "S");
	printw(" Elevation: %11.3f   Longitude: %9.3f %s\n",
	       e->elevation, fabs(lla->lon), (lla->lon > 0) ? "E" : "W");
	printw("     Range: %11.3f    Altitude: %11.3f\n",
	       e->range, lla->alt);
	printw("                              Speed: %11.3f\n",
	       /* RangeRate(s, site) */  len(e->objp->dx));
	
	
	delete lla;
	delete e;
}

// Utility function for printing a time and also printing the
// offset to that time.
void live_print_toff(char *label, double now, double when) {
	char buf[40];
	int to;
	int absto;

	to = (int) ((now - when) * 86400.0);
	absto = abs(to);
	
	strfsgptime(buf, 40, "%Y%m%d %H:%M:%S", when); 
	printw("%s %s (%c%02d:%02d:%02d)\n", label, buf,
	       (to < 0) ? '-' : '+', absto / 3600 , (absto / 60) % 60,
	       absto % 60);
}

// Print the details of the current pass, if any.
void live_pass(double now) {
	if (!p) return;

	move(15, 0);
	live_print_toff("  Pass Start:", now, p->min0);
	live_print_toff("Pass Maximum:", now, p->max);
	live_print_toff("    Pass End:", now, p->min1);
	live_print_toff("Pass Max Mag:", now, p->mmt);
}

// Position of the visual and radio boxes.
#define IROW 5
#define ICOL 53

// Display the visual magnitude info display.
void live_visual(double now) {
	Encounter *e;
	Sgp4Sat *sg;
	
	mvprintw(IROW, ICOL, "Visual");

	if (!s) return;

	mvprintw(IROW+2, ICOL, "Sun Elevation: %.2f", SunElevation(site, now));

	if (!s->hasmag()) {
		mvprintw(IROW+4, ICOL, "No Magnitude");
		return;
	}

	sg = getSgp4Sat(s);
	if (sg) {
		mvprintw(IROW+4, ICOL, "Intrinsic Mag: %.1f", sg->s->mag0);
	}

	e = new Encounter(s, site, now);

	if (isLit(e->objp)) {
		mvprintw(IROW+5, ICOL, "Current Mag: %.1f", s->calcmag(e));
	} else {
		mvprintw(IROW+5, ICOL, "Current Mag: Not Lit");
	}

	delete e;		
}

static FILE *lockfd = NULL;
static Object *lockeds;

// This handles a lock on to the satellite. Every SATLOCKSTEP seconds, it
// prints the current azimuth, elevation, and satellite name to lockfd.
void live_lock(double t) {
	Encounter *e;
	static int step = 0;
	int ns = 1;
	
	if (!lockfd) return;
	if (!obj) return;
	if (obj != lockeds) {
		pclose(lockfd);
		lockfd = NULL;
		return;
	}

	t += lockoffset * SECDAY;
	
	if (getenv("SATLOCKSTEP")) {
		ns = atoi(getenv("SATLOCKSTEP"));
	}

	if (step++ % ns) return;
		
	e = new Encounter(obj, site, t);
	e->calcmore();

	fprintf(lockfd, "%f %f %s\n", e->azimuth, e->elevation, obj->name);
	fflush(lockfd);
}
	
// This engages a lock on a satellite, opening lockfd as a stream to
// the SATLOCKPROG command.
static int lock() {
	if (!obj) return 0;
	
	if (lockfd) {
		pclose(lockfd);
		lockfd = NULL;
		status("Lock terminated.");
		return 0;
	}

	if (!getenv("SATLOCKPROG")) {
		status("Couldn't lock... SATLOCKPROG not defined.");
		return 0;
	}

	lockfd = popen(getenv("SATLOCKPROG"), "w");
	if (!lockfd) {
		status("Trouble executing SATLOCKPROG.");
		return 0;
	}

	status("Locked onto %s.", obj->name);
	lockeds = obj;
	
	setlinebuf(lockfd);
	return 0;
	
}

/////////////////////////// Key Commands ///////////////////////////
// These do an action, the return 1 to quit or 0 to quit the main
// loop.

// q
static int quit() {
	return 1;
}

// left, down
static int next() {
	if (l->cur->next) {
		t = l->next();
		status("Displaying next list entry.");
	} else {
		status("Already at last list entry.");
	}
	return 0;
}

// right, up
static int prev() {
	if (l->cur->prev) {
		t = l->prev();
		status("Displaying previous list entry.");
	} else {
		status("Already at first list entry.");
	}
	return 0;
}

// <
static int first() {
	l->reset();
	t = l->next();
	status("Displaying first list entry.");
	return 0;
}

// >
static int last() {
	l->reset();
	t = l->prev();
	status("Displaying last list entry.");
	return 0;
}

// +
static int inctimeoff() {
	timeoffset++;
	return 0;
}

// -
static int dectimeoff() {
	timeoffset--;
	return 0;
}

// '
static int inclockoff() {
	lockoffset++;
	return 0;
}

// ;
static int declockoff() {
	lockoffset--;
	return 0;
}

// space
// This finds either the next non-completed pass, or the next
// risen satellite.
static int nextup() {
	Encounter *e;
	double now;                  	
	
	now = sgp_now() + timeoffset * SECDAY;	
	
	while (t = l->next()) {
		obj = getObject(t);
		p = getPass(t);
		s = getSat(t);

		if (p) {
			if (now < p->min1) {
				status("Jumped to next non-completed pass.");
				return 0;
			}
			continue;
		}
		
		e = new Encounter(obj, site, now);
		if (e->elevation > 0) {
			delete e;
			status("Jumped to next elevated satellite.");
			return 0;
		}

		delete e;
	}

	t = l->prev();
	status("No more elevated satellites. (End of list.)");
	return 0;
}

// 1
int timelock1() {
	timelock = 1;
	status("Time locked to pass start.\n");
	return 0;
}

// 2
int timelock2() {
	timelock = 2;
	status("Time locked to pass maximum.\n");
	return 0;
}

// 3
int timelock3() {
	timelock = 3;
	status("Time locked to pass end.\n");
	return 0;
}

// 0
int timelock0() {
	timelock = 0;
	status("Time locked to real time.\n");
	return 0;
}

// v
int visualmode() {
	if (display == 1) {
		display = 0;
	} else {
		display = 1;
	}
	return 0;
}

// Keymap table.
struct Keymap keymap[] = {
	{'v', visualmode},
	{'0', timelock0},
        {'1', timelock1},
	{'2', timelock2},
	{'3', timelock3},
	{' ', nextup},
	{'l', lock},
	{'\'', inclockoff},
	{';', declockoff},
	{'+', inctimeoff},
	{'-', dectimeoff},
	{KEY_DOWN, next},
	{KEY_RIGHT, next},
	{KEY_UP, prev},
	{KEY_LEFT, prev},
	{KEY_HOME, first},
	{KEY_END, last},
	{'<', first},
	{'>', last},
	{'q', quit },
	{0, NULL}
};

// This implemets the live command.
int live(int argc, char **argv) {
	Options *o;
	double now;
	int c;
	struct Keymap *km;

	o = ParseGlobalOptions(argc, argv, NULL);
	l = ReadInput(o);

	site = GetSite();
	if (!site) return -1;
	
	t = l->next();
	if (!t) {
		error("empty list.\n");
		return 1;
	}

	if (o->visual) display = 1;

	// Setup the curses stuff
	initscr();
	halfdelay(10);
	noecho();
	nonl();
	keypad(stdscr, TRUE);

	status("Displaying first list entry.");

	// The main loop.
	while(1) {
		// determine the current time and object.
		now = sgp_now();
		s = getSat(t);
		obj = getObject(t);
		p = getPass(t);

		// Change the time if we're in a pass and a timelock
		// is set.
		if (p && timelock) switch(timelock) {
		case 1:
			now = p->min0;
			break;
		case 2:
			now = p->max;
			break;
		case 3:
			now = p->min1;
			break;
		}

		// Add in the timeoffset.
		now += timeoffset * SECDAY;

		// Display most displays.
		live_header(now);	
		live_name(now);
		live_ecivec(now);
		live_looklla(now);
		live_pass(now);

		// Display the appropriate visual or radio display
		
		switch(display) {
		case 1:
			live_visual(now);
			break;
		}

		// Handle a lock on.
		live_lock(sgp_now());

		// Display the status line.
		live_status();
		refresh();


		// Get a character. Run the appropriate function. Return
		// if it returns nonzero.
		c = getch();
		if (c == ERR) continue;
		
		km = keymap;

		while (km->c != 0) {
			if (c == km->c) break;
			km++;
		}

		if (km->c == 0) continue;
		if (km->func()) break;
	}

	refresh();
	endwin();

	return 0;
}
