/********************************************************/
/*               TVSats Module for SkyView              */
/*                                                      */
/*                  (c)1992 N P Hawkes                  */
/*                                                      */
/*         Displays geostationary TV satellites         */
/********************************************************/

#include "menu.h"
#include "dbox.h"
#include "bbc.h"
#include "wimpt.h"
#include "dbox.h"
#include "sprite.h"
#include "res.h"
#include "resspr.h"
#include "werr.h"

#include "sv_header.h"
#include "tvsats.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <ctype.h>

/********************************************************/
/*                     Constants                        */
/********************************************************/
#define RHO 0.151             /* Ratio of Earth's radius*/
                              /* to radius of geostat-  */
                              /* ionary orbit.          */
#define DISP_NAME "TV sats."  /* Entry in Display menu. */
#define SEL_NAME  "TV sat."   /* Entry in Select menu.  */

#define MAX_SATS 25           /* Max no. of satellites. */
#define FILE_NAME "SatData"   /* Name of data file.     */

#define FMT_LEN  256          /* Len. of format string. */
#define NAME_LEN  15          /* Length of name field.  */
#define DESC_LEN  30          /* Max len. of decsription*/

#define SPR_NAME "tvsat"      /* Name of sprite.        */
#define SPR_X0 -12            /* Bounding box of        */
#define SPR_Y0 -12            /* sprite, OS units,      */
#define SPR_X1  14            /* relative to            */
#define SPR_Y1  14            /* plotting position.     */
#define SPR_COL  5            /* Sprite x offset.       */
#define SPR_ROW  4            /* Sprite y offset.       */
#define SPR_MODE MODE_20      /* Sprite mode.           */

/********************************************************/
/*                New types of variable.                */
/********************************************************/
/* Structure describing one satellite.                  */
typedef struct {
  char name[NAME_LEN+1];  /* Name of satellite.         */
  REAL longit;            /* Longitude of satellite.    */
  char descr[DESC_LEN+1]; /* Description.               */
  int  horiz_id;          /* id in Horiz window.        */
  int  vert_id;           /* id in Vert window.         */
} satstr;

/********************************************************/
/*                  Global Variables                    */
/********************************************************/
static BOOL display_flag = FALSE;  /* 'Enabled' flag.   */
static int moduleid;               /* Module ID.        */

static FILE *fileptr;              /* Ptr to data file. */
static char fmt[FMT_LEN];          /* For format string.*/
static satstr sat_data[MAX_SATS];  /* Main data store.  */
static int sat_count = 0;          /* No. of satellites.*/

static menu sat_menu;  /* For selecting a satellite.    */

static double cos_anglimit = RHO;
/* Satellite is below horizon if cos of angle between   */
/* vector to it and vector to observer is less than     */
/* cos_anglimit (where origin is centre of Earth).      */ 

static double delta;   /* For diff between longitude of */
                       /* observer & that of satellite. */
static double dlatit;  /* Double version of latitude of */
                       /* observer.                     */
static double cosbeta; /* For cosine of angle OCS, where*/
                       /* O=observer, C=centre of earth,*/
                       /* S=satellite.                  */

static sprite_id spr_id;  /* ID of satellite sprite.    */

/********************************************************/
/*                 Function Prototypes                  */
/********************************************************/
static void tvsats_buildfn(void);
static void tvsats_selectfn(selectfn_reasoncode reason);
static BOOL tvsats_displayfn(BOOL *enabptr);
static os_error *tvsats_plotfn(int x, int y, int id);
static void tvsats_infofn(int id);
static BOOL read_satdata(satstr *sptr);
static char *trim(char *str);
static void tvsat_altaz(int id, REAL *altptr, REAL *azimptr);
static void read_optional(char *string, int count);

/********************************************************/
/*                Initialisation Function               */
/********************************************************/
BOOL tvsats_initfn(int moduleno, modulestr *tvsats)
{
  int i;
  sprite_id nametype_id;
  os_error *errptr;

/* Record the module number of this module.             */
  moduleid = moduleno;

/* Open data file.                                      */
  fileptr = res_openfile(FILE_NAME, "r");
  if (fileptr==NULL) return FALSE;

/* Construct the format string for reading the data.    */
  sprintf(fmt, "%%%ic%%f %%c", NAME_LEN);

/* Read data, one satellite at a time.  Stop on first   */
/* error (probably EOF).  Stay within array bounds.     */
  while (sat_count < MAX_SATS && \
         read_satdata(&sat_data[sat_count]))
    sat_count++;

/* Close file.  Fatal error if it won't close.          */
  if (fclose(fileptr) != 0)
    werr(FATAL, "TVSats Error: Can't close Data file");

/* Quit if no items have been read.                     */
  if (sat_count == 0) return FALSE;

/* Build menu for Select function.                      */
/* Use menu_new for first item, menu_extend thereafter. */
  sat_menu = menu_new(SEL_NAME ":", sat_data[0].name);
  if (sat_menu == NULL) return FALSE;
  for (i=1; i<sat_count; i++)
    menu_extend(sat_menu, sat_data[i].name);

/* Fill in the fields of the modulestr.                 */
  tvsats->buildfn  = tvsats_buildfn;
  tvsats->selectfn = tvsats_selectfn;
  tvsats->dispfn   = tvsats_displayfn;
  tvsats->infofn   = tvsats_infofn;
  tvsats->initial  = display_flag;
  tvsats->display_entry = DISP_NAME;
  tvsats->display_menu  = NULL;
  tvsats->select_entry  = SEL_NAME;
  tvsats->select_menu   = sat_menu;

/* Set up the sprite_id structure which identifies the  */
/* satellite sprite.                                    */
/* First set up a name-type identifier.                 */
  nametype_id.s.name = SPR_NAME;
  nametype_id.tag    = sprite_id_name;
/* Then get the address of the sprite, and put it in    */
/* the global sprite identifier spr_id.                 */
  errptr = sprite_select_rp(resspr_area(), &nametype_id, &spr_id.s.addr);
  if (errptr != NULL) return FALSE;
/* Finally tag the identifier spr_id as address-type.  */
  spr_id.tag = sprite_id_addr;

  return TRUE;
}

/*------------------------------------------------------*/
/*    Function to read in a line of satellite data.     */
/*  Name, longitude and E/W designator must be present. */
/*                Description is optional.              */
/*  Return TRUE if at least the minimum data have been  */
/*          sucessfully read, FALSE otherwise.          */
/*------------------------------------------------------*/
static BOOL read_satdata(satstr *sptr)
{
  /* Temp storage for sat's longitude (in float form):  */
  float flongit;
  /* Temp storage for char which designates E or W:     */
  char ew;

/* Read name, longitude, and E/W.                       */
/* Return FALSE if error (probably EOF) occurs.         */
  if (fscanf(fileptr, fmt,
             sptr->name,
             &flongit,
             &ew)
                 != 3)
    return FALSE;

/* Put a terminating \0 into the name string.  Then     */
/* trim trailing blanks.                                */
  sptr->name[NAME_LEN] = '\0';
  trim(sptr->name);

/* Convert longit to radians. <0 if East.               */
  if (ew == 'E' || ew == 'e') flongit = -flongit;
  sptr->longit = (REAL)flongit * CONV;

/* Read description, if it is present.                  */
/* Trim any trailing blanks.                            */
  read_optional(sptr->descr, DESC_LEN);
  trim(sptr->descr);

  return TRUE;
}

/*------------------------------------------------------*/
/* Function which reads optional description string at  */
/* end of current line.  Reads at most 'count' chars.   */
/* Returns null string if description is absent.        */
/*------------------------------------------------------*/
static void read_optional(char *string, int count)
{
  int c;

/* Skip any leading white space (except '\n').          */
  do
    c = fgetc(fileptr);
  while (isspace(c) && c != '\n');

/* Read characters into string until:                   */
/*      'count' chars have been read in,                */
/* or   \n is seen (in which case do not store \n),     */
/* or   EOF is seen.                                    */
  while (count>0  &&  c!='\n'  &&  c!=EOF)
  {
    *string++ = c;
    count--;
    c = fgetc(fileptr);
  }

/* Add terminating \0.                                  */
  *string = '\0';

/* If neither \n or EOF was seen, discard remainder     */
/* of current line.                                     */
  while (c!='\n' && c!=EOF) c = fgetc(fileptr);

  return;
}

/*------------------------------------------------------*/
/*  Function which trims trailing blanks from a string. */
/*------------------------------------------------------*/
static char *trim(char *str)
{
  char *ptr;

  for (ptr = str+strlen(str); ptr>str && *(ptr-1)==' '; ptr--)
    ;
  *ptr = '\0';

  return str;
}
/********************************************************/
/*                 List-Building Function               */
/********************************************************/
static void tvsats_buildfn(void)
{
  int i;
  plotobj tvsat;

  /* Bounding box of plotting symbol, relative to       */
  /* position of symbol:                                */
  static wimp_box size = {SPR_X0, SPR_Y0, SPR_X1, SPR_Y1};

/* For each satellite in turn:                          */
  for (i=0; i<sat_count; i++)
  {

    /* Calculate cosine of angle OCS (Observer, Centre  */
    /* of earth, Satellite).                            */
    delta = (double)ob_data.longit - (double)sat_data[i].longit;
    dlatit= (double)ob_data.latit;
    cosbeta = cos(dlatit)*cos(delta);
    if (cosbeta >  1.0) cosbeta =  1.0;
    if (cosbeta < -1.0) cosbeta = -1.0;
    
    /* See if satellite is below the horizon, by        */
    /* comparing cosbeta with limit.                    */
    if (cosbeta < cos_anglimit)
    {
      /* Do not bother to offer this one to main program*/
      /* Just set horiz and vert ids to NOWHERE, and    */
      /* continue with next satellite.                  */
      sat_data[i].horiz_id = NOWHERE;
      sat_data[i].vert_id  = NOWHERE;
      continue;
    }

    /* Build plotobj.                                   */
    tvsat.id = i;
    tvsat.module = moduleid;
    tvsat.plotfn  = tvsats_plotfn;
    tvsat_altaz(i, &tvsat.alt, &tvsat.azim);
    tvsat.size = size;

    /* Offer this to the main program.                  */
    addobj(tvsat, &sat_data[i].horiz_id, &sat_data[i].vert_id);

  }

  return;
}

/*------------------------------------------------------*/
/*   Function to calculate alt & azim of satellite i.   */
/*------------------------------------------------------*/
static void tvsat_altaz(int id, REAL *altptr, REAL *azimptr)
{
/* Assumes delta, dlatit and cosbeta are already valid  */
/* for this satellite.                                  */

  double sinbeta;
  double cosalt, alt, cosazim, azim;
  static double epsilon = 8.7E-04;

/* Special case when observer is almost directly       */
/* beneath satellite.                                  */
  if (fabs(delta) < epsilon && fabs(dlatit) < epsilon)
  {
    *altptr  = PIby2;
    *azimptr = (REAL)0.0;
    return;
  }

  sinbeta = sin(acos(cosbeta));

  cosalt  = sinbeta/sqrt(1.0+RHO*RHO-2.0*RHO*cosbeta);
  if (cosalt >  1.0) cosalt =  1.0;
  if (cosalt < -1.0) cosalt = -1.0;
  cosazim = -tan(dlatit)*cosbeta/sinbeta;
  if (cosazim >  1.0) cosazim =  1.0;
  if (cosazim < -1.0) cosazim = -1.0;

  alt  = acos(cosalt);
  azim = (delta >= 0 ? acos(cosazim): -acos(cosazim));

  *altptr = (REAL)alt;
  *azimptr= (azim >= 0 ? (REAL)azim: (REAL)2.0*PI + (REAL)azim);

  return;
}

  
/********************************************************/
/*              Object-Selecting Function               */
/********************************************************/
static void tvsats_selectfn(selectfn_reasoncode reason)
{
  int menu_hit, sat_id;
  REAL sat_longit;

  switch (reason)
  {
    case new_selection:
      /* Look at list of menu hits to find which        */
      /* satellite is to be selected.                   */
      menu_hit = module_submenu_hits[0];

      /* If no hits on Satellite menu, flag the failure */
      /* and return.  Also return if hit is off the end.*/
      if (menu_hit <= 0 || menu_hit > sat_count)
      {
        selection.selected_OK = FALSE;
        return;
      }
        
      /* Fill in Selection details for requested        */
      /* satellite.  Allow for menu entries being       */
      /* indexed from 1 instead of 0.                   */
      sat_id = menu_hit - 1;
      selection.id          = sat_id;
      selection.selected_OK = TRUE;
      selection.horiz_id    = sat_data[sat_id].horiz_id;
      selection.vert_id     = sat_data[sat_id].vert_id;
      selection.now         = selection.horiz_id != NOWHERE || \
                              selection.vert_id  != NOWHERE;
      selection.rising      = FALSE;
      selection.setting     = FALSE;
      selection.culminating = FALSE;
      break;

    case window_selection:
      /* Inspect id field to find out which satellite   */
      /* is to be selected.                             */
      sat_id = selection.id;
      selection.horiz_id    = sat_data[sat_id].horiz_id;
      selection.vert_id     = sat_data[sat_id].vert_id;
      selection.now         = selection.horiz_id != NOWHERE || \
                              selection.vert_id  != NOWHERE;
      selection.rising      = FALSE;
      selection.setting     = FALSE;
      selection.culminating = FALSE;
      break;

    case recalculate_data:
      /* Observer details have changed, and plotting    */
      /* lists have been rebuilt. Update Selection data.*/
      sat_id = selection.id;
      selection.horiz_id    = sat_data[sat_id].horiz_id;
      selection.vert_id     = sat_data[sat_id].vert_id;
      selection.now         = selection.horiz_id != NOWHERE || \
                              selection.vert_id  != NOWHERE;
      break;

    case timeonly_recalculate:
      /* As above, but only the time of day (and        */
      /* possibly the direction of view - this is of no */
      /* interest) have changed.                        */
      sat_id = selection.id;
      selection.horiz_id    = sat_data[sat_id].horiz_id;
      selection.vert_id     = sat_data[sat_id].vert_id;
      break;

    case sel_info_request:
      /* Write info into text buffer.                   */
      sat_id = selection.id;
      sat_longit = sat_data[sat_id].longit/CONV;
      sprintf(infoptr,"%s%s%s%.1f%s%s%s%s%s",
      "TV Satellite (Geostationary)\n",
      sat_data[sat_id].name, "\n",
      fabs((double)sat_longit)," ",
      (sat_longit>0? "W\n" : "E\n"),
      sat_data[sat_id].descr,"\n",
      (selection.now? "Always visible from here": 
                      "Never visible from here"));
      break;

    default:
      /* Unknown option.  Do nothing.                   */
      break;

    }

  return;
}


/********************************************************/
/*                   Display Function                   */
/********************************************************/
static BOOL tvsats_displayfn(BOOL *enabptr)
{
/* Toggle Enable/Disable flag.                          */
  display_flag = !display_flag;

/* Inform main program of new status.                   */
  *enabptr = display_flag;

/* Windows will always need updating.                   */
  return TRUE;
}


/********************************************************/
/*                   Plotting Function                  */
/********************************************************/
static os_error *tvsats_plotfn(int x, int y, int id)
{
  return sv_plotsprite(&spr_id, SPR_MODE, x, y, SPR_COL, SPR_ROW);
}


/********************************************************/
/*                     Info Function                    */
/********************************************************/
static void tvsats_infofn(int id)
{
  REAL longit = sat_data[id].longit/CONV;

  sprintf(infoptr,"%s%s%s%.1f%s%s%s",
                  "TV Satellite (Geostationary)\n",
                   sat_data[id].name, "\n",
                   fabs((double)longit)," ",
                   (longit>0? "W\n" : "E\n"),
                   sat_data[id].descr);

  return;
}
