/*
 *****************************************************
 *
 *  SaVi by Robert Thurman (thurman@geom.umn.edu) and
 *          Patrick Worfolk (worfolk@alum.mit.edu).
 *
 *  Copyright (c) 1997 by The Geometry Center.
 *  This file is part of SaVi.  SaVi is free software;
 *  you can redistribute it and/or modify it only under
 *  the terms given in the file COPYRIGHT which you should
 *  have received along with this file.  SaVi may be
 *  obtained from:
 *  http://savi.sourceforge.net/
 *  http://www.geom.uiuc.edu/locate/SaVi
 *
 *****************************************************
 *
 * sats.c
 *
 * $Id: sats.c,v 1.20 2005/02/03 02:22:28 lloydwood Exp $
 */
#include <stdlib.h>
#include <sys/time.h>
#include <math.h>

/* for select */
/*
#include <unistd.h>
#include <sys/types.h>
#include <bstring.h>
*/

#include "globals.h"
#include "constants.h"
#include "utils.h"
#include "coverage_vis.h"
#include "fisheye.h"
#include "gv_utils.h"
#include "Satellite.h"
#include "sats.h"
#include "stats_utils.h"
#include "savi.h"


extern void time_update(const double ttime);

unsigned int geomview_flag = FALSE;

static double direction; /* 1.0 = FORWARDS, -1.0 = BACKWARDS */
/*
 * Central body parameter - global from the parameters panel.  It should
 * be initialized in the following routine so it can be reset at runtime
 */
static CentralBody CB =
  { EARTH_RADIUS, EARTH_ROTATION_RATE, EARTH_MU, EARTH_OBLATENESS };

static Constellation constellation = { 0, 0, 0, (Satellite_list) NULL, &CB };

static double ttime = 0.0;
static unsigned int realtime_flag = FALSE;
static double realtime = 0;

static Satellite sats_n(int n);
static char OK_str[] = "OK";

static const int Forward = 1.0;
static const int Backward = -1.0;

/*
 * forwards_cmd
 */
char *
forwards_cmd(int argc, char *argv[])
{
  if (!motion) {
    motion = TRUE;
    direction = Forward;
    set_realtime();
    return OK_str;
  }
  return EMPTY_str;
}

/*
 * backwards_cmd
 */
char *
backwards_cmd(int argc, char *argv[])
{
  if (!motion) {
    motion = TRUE;
    direction = Backward;
    set_realtime();
    return OK_str;
  }
  return EMPTY_str;
}

/*
 * forwards_step_cmd
 */
char *
forwards_step_cmd(int argc, char *argv[])
{
  if (!motion) {
    motion = TRUE;
    single_step = TRUE;
    direction = Forward;
    sats_update();		/* call twice to compute and move */
    sats_update();
    return OK_str;
  }
  return EMPTY_str;
}

/*
 * backwards_step_cmd
 */
char *
backwards_step_cmd(int argc, char *argv[])
{
  if (!motion) {
    motion = TRUE;
    single_step = TRUE;
    direction = Backward;
    sats_update();		/* call twice to compute and move */
    sats_update();
    return OK_str;
  }
  return EMPTY_str;
}

/*
 * reset_cmd
 */
char *
reset_cmd(int argc, char *argv[])
{
  reset = TRUE;
  return OK_str;
}


/*
 * stop_cmd
 */
char *
stop_cmd(int argc, char *argv[])
{
  if (motion) {
    sats_update();		/* call twice to compute and move */
    sats_update();
    motion = FALSE;
    return OK_str;
  }
  return EMPTY_str;
}


/*
 * set_realtime
 */
void
set_realtime()
{
  struct timeval tv;
  gettimeofday(&tv, NULL);
  realtime = tv.tv_sec + tv.tv_usec / 1000000.0;;
}

/*
 * realtime_on_cmd
 */
char *
realtime_on_cmd(int argc, char *argv[])
{
  if (realtime_flag)
    return EMPTY_str;

  realtime_flag = TRUE;
  set_realtime();

  return EMPTY_str;
}

/*
 * realtime_off_cmd
 */
char *
realtime_off_cmd(int argc, char *argv[])
{
  if (!realtime_flag)
    return EMPTY_str;

  realtime_flag = FALSE;

  return EMPTY_str;
}

/*
 * geomview_on_cmd
 */
char *
geomview_on_cmd(int argc, char *argv[])
{

  if (!geomview_module)
    return EMPTY_str;
  geomview_set_flag(TRUE);

  /* plot all satellites */
  gv_begin();
  gv_trans_list_create(constellation.satellites);
  earth_place(ttime, constellation.pcb);
  gv_end();

  return EMPTY_str;
}

/*
 * geomview_off
 */
char *
geomview_off_cmd(int argc, char *argv[])
{
  geomview_set_flag(FALSE);
  return EMPTY_str;
}

/*
 * geomview_set_flag(int flag)
 */
void
geomview_set_flag(int flag)
{
  geomview_flag = flag;
}

/*
 * set_time()
 *
 * Sets time variable and makes sure window displays it
 */
void
set_time(double t)
{
  ttime = t;
  time_update(t);
}

/*
 * current_time()
 *
 * Returns current value of time variable.
 */
double
current_time()
{
  return (ttime);
}

/*
 * get_constellation
 *
 * Returns a pointer to the constellation
 */
Constellation *
get_constellation()
{
  return &constellation;
}


/*
 * get_central_body_radius
 *
 */
double
get_central_body_radius()
{
  return constellation.pcb->radius;
}

/*
 * satellite_init
 *
 * Create initial example satellite
 */
void
satellite_init()
{
  Satellite s;

  OrbitalElements oe = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };

  /* put at some fixed distance above the Earth */
  oe.a = 1.2 * get_central_body_radius();

  /* install one satellite */
  s = satellite_create(0, &oe, constellation.pcb);
  constellation_add(&constellation, s);
  constellation_compute_positions(&constellation, ttime);

  /* display all satellites only if geomview is running */
  /* place first so that they are created in the correct positions */
  if (geomview_module) {
    gv_trans_list_create(constellation.satellites);
    gv_sat_list_create(&constellation);
  }
}

/*
 * sats_init
 *
 * Create initial configuration
 */
void
sats_init()
{
  /* set time */
  set_time(0.0);

  /* set size of coverage map */
  if (coverage_large_map) {
      Image_Width = IMAGE_LARGE_WIDTH;
      Image_Height = IMAGE_LARGE_HEIGHT;
  } else {
      Image_Width = IMAGE_WIDTH;
      Image_Height = IMAGE_HEIGHT;
  }

}


/*
 * sats_update
 *
 * loop called repeatedly to update the configuration and
 * display of the satellites
 *
 */
unsigned int
sats_update()
{
  /*
   * Unit of time is second-dev-tes.
   */
  unsigned int i;
  static unsigned int computed = FALSE;
  static double computed_time = 0.0;
  static double old_delta_t = 0.0;
  static double old_ttime = 0.0;
  static int old_direction = 0;

  double oldt;

  if (reset) {
    set_time(0.0);
    computed_time = 0.0;
    init_coverage_time();
    computed = FALSE;
    reset = FALSE;

    constellation_compute_positions(&constellation, ttime);

    tracks_compute(constellation.satellites, constellation.pcb);
    fisheye_display(constellation.satellites, FALSE, constellation.pcb);

    clear_coverage();
    coverage_compute(constellation.satellites, FALSE, constellation.pcb);

    if (geomview_flag) {
      gv_start();
      if (transforms_needed) {
	gv_trans_list_create(constellation.satellites);
      }
      for (i = 0; i < N_VIEW_MODULES; i++) {
	if (view_modules[i].relocate_fn)
	  view_modules[i].relocate_fn(&constellation);
      }
      gv_delayed_view_update();
      earth_place(ttime, constellation.pcb);
      /* updates display, sends scratchfile if necessary. */
      coverage_display(constellation.satellites, FALSE, constellation.pcb);
      gv_stop();
      gv_set_ready();
    } else {
      /* we're not talking to Geomview, but still need to draw coverage */
      coverage_display(constellation.satellites, FALSE, constellation.pcb);
    }

    project_tracks_reset();

  } else if (motion) {

    if (computed && !realtime_flag &&
	((old_ttime != ttime) ||
	 (old_delta_t != delta_t) ||
	 (old_direction != direction))) computed = FALSE;

    if (!computed) {

      if (!realtime_flag || single_step) {
	computed_time = ttime + direction * delta_t;
	old_ttime = ttime;
	old_delta_t = delta_t;
	old_direction = direction;
      } else {
	oldt = realtime;
	set_realtime();
	computed_time = ttime + direction * (realtime - oldt) * delta_t;
      }
      constellation_compute_positions(&constellation, computed_time);
      coverage_decay();
      coverage_compute(constellation.satellites, FALSE, constellation.pcb);
      tracks_compute(constellation.satellites, constellation.pcb);
      computed = TRUE;

      /* Pause briefly to avoid saturating the CPU. */
      millisleep(1);

    } else {

      if (!geomview_flag || gv_ready()) {
	set_time(computed_time);

	fisheye_display(constellation.satellites, FALSE, constellation.pcb);

	/* plot all satellites */
	if (geomview_flag) {
	  gv_start();
	  if (transforms_needed) {
	    gv_trans_list_create(constellation.satellites);
	  }

	  for (i = 0; i < N_VIEW_MODULES; i++) {
	    if (view_modules[i].relocate_fn)
	      view_modules[i].relocate_fn(&constellation);
	  }
	  gv_delayed_view_update();
	  earth_place(ttime, constellation.pcb);
	  /* updates display, sends scratchfile if necessary. */
	  coverage_display(constellation.satellites, FALSE, constellation.pcb);
	  gv_stop();
	  gv_set_ready();
	} else {
	  /* we're not talking to Geomview, but still need to draw coverage */
          coverage_display(constellation.satellites, FALSE, constellation.pcb);
        }

	if (single_step) {
	  single_step = FALSE;
	  motion = FALSE;
	}

	computed = FALSE;
      }
    }
  } else {
    /* If nothing's happening, pause 20 milliseconds to avoid
       saturating the CPU. */
    millisleep(20);
  }
  return TRUE;
}


/*
 * sats_new
 *
 * Adds a new satellite and makes it current
 */
char *
sats_new_cmd(int argc, char *argv[])
{
  Satellite s;
  OrbitalElements oe = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 };
  static char result[10];

  oe.a = CB.radius * 1.2;

  s = satellite_create(0, &oe, constellation.pcb);
  constellation_add(&constellation, s);

  satellite_compute_position(s, ttime, constellation.pcb);
  if (geomview_module) {
    gv_begin();
    gv_trans_create(s);
    gv_sat_create(s, &constellation);
    gv_end();
  }

  sprintf(result, "%d", s->id);
  return result;
}

/*
 * sats_n
 *
 * Returns nth satellite (starting from 0)
 */
static Satellite
sats_n(int n)
{
  Satellite_list sl = constellation.satellites;

  while (sl && (n > 0)) {
    sl = sl->next;
    n--;
  }
  if (sl)
    return sl->s;

  return NULL;
}

/*
 * sats_get_cmd
 *
 * Returns orbital elements and properties of nth satellite in list.
 *
 * Don't forget: User interface angles are in degrees, but internally we
 * like radians.
 *
 */
char *
sats_get_cmd(int argc, char *argv[])
{
  int n = atoi(argv[2]);
  Satellite s = sats_n(n);
  static char result[300];

  if (s) {
    sprintf(result,
	    "%10.2f %10.4f %10.3f %10.3f %10.3f %10.2f "
	    "%10.3f %10.4f %10.4f %10.2f %10.2f %d %d %d %d",
	    s->oe.a,
	    s->oe.e,
	    s->oe.i * RAD_TO_DEG,
	    s->oe.Omega * RAD_TO_DEG,
	    s->oe.omega * RAD_TO_DEG,
	    s->oe.T,
	    oe_to_period(&(s->oe), constellation.pcb) / 60,
	    oe_to_nodal_precession(&(s->oe),
		     constellation.pcb) * RAD_TO_DEG * SECS_PER_DAY,
	    oe_to_apsidal_rotation(&(s->oe),
		     constellation.pcb) * RAD_TO_DEG * SECS_PER_DAY,
	    oe_to_apoapsis_altitude(&(s->oe),
		     constellation.pcb),
	    oe_to_periapsis_altitude(&(s->oe), constellation.pcb),
	    s->can_display_satellite, s->can_display_orbit,
	    s->can_display_cone, s->can_display_footprint);

    return result;
  }
  return EMPTY_str;
}

/*
 * sats_set_cmd
 *
 * Sets the orbital elements of a particular satellite.
 *
 * Don't forget: User interface angles are in degrees, but internally we
 * like radians.
 */
char *
sats_set_cmd(int argc, char *argv[])
{
  unsigned int j;
  int n = atoi(argv[2]);
  Constellation *pconstellation = get_constellation();

  Satellite s = sats_n(n);

  if (s) {
    s->oe.a = atof(argv[3]);
    s->oe.e = atof(argv[4]);
    s->oe.i = atof(argv[5]) * DEG_TO_RAD;
    s->oe.Omega = atof(argv[6]) * DEG_TO_RAD;
    s->oe.omega = atof(argv[7]) * DEG_TO_RAD;
    s->oe.T = atof(argv[8]);
    satellite_oe_edited(s, pconstellation->pcb);

    if (atoi(argv[9])) {
      /* allow satellite to be viewed */
      if (!s->can_display_satellite) {
	s->can_display_satellite = 1;
	pconstellation->n_view_sats++;
      }
    } else {
      /* don't allow satellite to be viewed */
      if (s->can_display_satellite) {
	s->can_display_satellite = 0;
	pconstellation->n_view_sats--;
      }
    }
    s->can_display_satellite = atoi(argv[9]) ? 1 : 0;
    s->can_display_orbit = atoi(argv[10]) ? 1 : 0;
    s->can_display_cone = atoi(argv[11]) ? 1 : 0;
    s->can_display_footprint = atoi(argv[12]) ? 1 : 0;

    satellite_compute_position(s, ttime, pconstellation->pcb);
    if (geomview_module) {
      gv_begin();
      gv_trans_create(s);
      for (j = 0; j < N_VIEW_MODULES; j++)
	if (view_modules[j].display_fn)
	  view_modules[j].display_fn(s, pconstellation);
      gv_end();
    }
  }
  return EMPTY_str;
}

/*
 * sats_tag_cmd
 *
 * Sets the nth satellite as distinguished.
 * This routine previously untagged all the others, which
 * is wrong - shift-click selects, and should therefore tag,
 * multiple satellites.
 */
char *
sats_tag_cmd(int argc, char *argv[])
{
  int n = atoi(argv[2]);
  Constellation *pconstellation = get_constellation();
  Satellite_list sl = pconstellation->satellites;

  pconstellation->n_tag_sats = 0;

  while (sl) {
    if (n == 0) {
      if (!(sl->s->tag)) {
	sl->s->tag = 1;
	if (geomview_module) {
	  gv_begin();
	  gv_sat_create(sl->s, pconstellation);
	  gv_end();
	}
      }
      pconstellation->n_tag_sats = 1;
    } else {
      if (sl->s->tag) {
	sl->s->tag = 0;
	if (geomview_module) {
	  gv_begin();
	  gv_sat_create(sl->s, pconstellation);
	  gv_end();
	}
      }
    }
    n--;
    sl = sl->next;
  }

  return EMPTY_str;
}

/*
 * sats_untag_cmd
 *
 * Untags the nth satellite
 */
char *
sats_untag_cmd(int argc, char *argv[])
{
  int n = atoi(argv[2]);
  Satellite_list sl = constellation.satellites;

  while (sl && (n > 0)) {
    sl = sl->next;
    n--;
  }

  if (sl && (sl->s->tag)) {
    (constellation.n_tag_sats)--;
    sl->s->tag = 0;
    if (geomview_module) {
      gv_begin();
      gv_sat_create(sl->s, &constellation);
      gv_end();
    }
  }

  return EMPTY_str;
}

/*
 * sats_delete_cmd
 *
 * Deletes satellite n
 */
char *
sats_delete_cmd(int argc, char *argv[])
{
  int n = atoi(argv[2]);
  int i;
  Satellite s = sats_n(n);

  if (s) {
    constellation_destroy_element(&constellation, s->id);
    if (geomview_module) {
      gv_begin();
      for (i = 0; i < N_VIEW_MODULES; i++)
	if (view_modules[i].delete_fn)
	  view_modules[i].delete_fn(s);
      gv_end();
    }
  }
  return EMPTY_str;
}

/*
 * sats_copy
 *
 */
char *
sats_copy_cmd(int argc, char *argv[])
{
  static char result[10];
  int n = atoi(argv[2]);
  Satellite old_s = sats_n(n);
  Satellite s = old_s;

  if (old_s) {
    s = satellite_create(0, &(old_s->oe), constellation.pcb);
    constellation_add(&constellation, s);
    satellite_compute_position(s, ttime, constellation.pcb);
    if (geomview_module) {
      gv_begin();
      gv_trans_create(s);
      gv_sat_create(s, &constellation);
      gv_end();
    }
  }

  if (s) {
    sprintf(result, "%d", s->id);
  }

  return result;
}

/*
 * sats_load_cmd
 *
 * Don't forget: User interface angles are in degrees, but internally we
 * like radians.
 */
char *
sats_load_cmd(int argc, char *argv[])
{
  double a = atof(argv[2]);
  double e = atof(argv[3]);
  double i = atof(argv[4]);
  double Omega = atof(argv[5]);
  double omega = atof(argv[6]);
  double T = atof(argv[7]);
  Satellite s;
  int n;
  static char result[200];

  OrbitalElements oe;

  oe.a = a;
  oe.e = e;
  oe.i = i * DEG_TO_RAD;
  oe.Omega = Omega * DEG_TO_RAD;
  oe.omega = omega * DEG_TO_RAD;
  oe.T = T;

  s = satellite_create(0, &oe, constellation.pcb);
  n = constellation_add(&constellation, s);
  satellite_compute_position(s, ttime, constellation.pcb);

  if (geomview_module) {
    gv_start();
    gv_trans_create(s);
    gv_sat_create(s, &constellation);
    gv_stop();
  }

  sprintf(result, "%d", n);
  return result;
}

/*
 * sats_delete_all
 *
 * Deletes all satellites in the satellite list
 */
char *
sats_delete_all_cmd(int argc, char *argv[])
{
  unsigned int i;

  while (constellation.satellites)
    constellation_destroy_element(&constellation,
				  constellation.satellites->s->id);

  if (geomview_module) {
    gv_start();
    for (i = 0; i < N_VIEW_MODULES; i++)
      if (view_modules[i].gv_delete_fn)
	view_modules[i].gv_delete_fn();
    gv_stop();
  }

  return EMPTY_str;
}


/*
 * sats_debug_cmd
 *
 * Dumps satellite list to stderr
 */
char *
sats_debug_cmd(int argc, char *argv[])
{
  Satellite_list sl = constellation.satellites;

  fprintf(stderr, "Satellites:\n");
  while (sl) {
    fprintf(stderr, "  %d %g %g %g %g %g %g",
	    sl->s->id, sl->s->oe.a, sl->s->oe.e, sl->s->oe.i,
	    sl->s->oe.Omega, sl->s->oe.omega, sl->s->oe.T);
    fprintf(stderr, "\n");
    sl = sl->next;
  }
  return EMPTY_str;
}

/*
 * reset_params_cmd
 *
 * Resets values of central body
 */
char *
reset_params_cmd(int argc, char *argv[])
{
  CentralBody *pcb = constellation.pcb;

  pcb->radius = EARTH_RADIUS;
  pcb->rotation_rate = EARTH_ROTATION_RATE;
  pcb->mu = EARTH_MU;
  pcb->J2 = EARTH_OBLATENESS;

  return EMPTY_str;
}


/*
 * exit_cmd
 *
 * Clean up everything
 */
char *
exit_cmd(int argc, char *argv[])
{
  unsigned int i;

  (void) sats_delete_all_cmd(argc, argv);

  if (!geomview_module)
    return EMPTY_str;

  gv_start();
  for (i = 0; i < N_VIEW_MODULES; i++) {
    if (view_modules[i].off_cmd) {
      (void) view_modules[i].off_cmd(argc, argv);
    }
  }

  earth_off_cmd(argc, argv);
  fancy_off_cmd(argc, argv);

  gv_stop();

  coverage_tempfile_cleanup();

  return EMPTY_str;
}


void
gv_delayed_view_update()
{
  Constellation *pconstellation;
  unsigned int i;

  if (!geomview_module)
    return;

  pconstellation = get_constellation();
  for (i = 0; i < N_VIEW_MODULES; i++)
    if (view_modules[i].write_geom_fn)
      view_modules[i].write_geom_fn(pconstellation);
}


/*
 * Return the version string
 */
char *
version_cmd(int argc, char *argv[])
{
  return Version;
}
