/********************************************************/
/*               Moon Module for SkyView                */
/*                                                      */
/*                  (c)1992 N P Hawkes                  */
/*                                                      */
/*                  Displays the Moon.                  */
/********************************************************/

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

#include "sv_header.h"
#include "datime.h"
#include "radec.h"
#include "ecliptic.h"
#include "riset.h"
#include "moon.h"

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

/********************************************************/
/*                     Constants                        */
/********************************************************/
#define DISP_NAME "The Moon" /* Entry in Display menu.  */
#define SEL_NAME  "The Moon" /* Entry in Select menu.   */

/*  Set effective altitude of horizon for calculations  */
/* of rising and setting.                               */
#define HORALT (REAL)0.0088

#define CONVERGE    5       /* Convergence criterion in */
                            /* phenomenon calculations. */
#define MAX_ITER    5       /* Max no. of iterations.   */

#define NUM_SPR     4       /* Number of different Moon */
                            /* symbols (for diff. ages).*/
#define SPRNAM_MAX 13       /* Max len of spr name, +1. */

/********************************************************/
/*                  Global Variables                    */
/********************************************************/
static BOOL display_flag = TRUE;   /* 'Enabled' flag.   */
static int moduleid;    /* Module ID.                   */
static int horiz_id;    /* Moon's ID in Horiz window.   */
static int vert_id;     /* Moon's ID in Vert window.    */
static REAL moonra;     /* Right Ascension of Moon.     */
static REAL moondec;    /* Declination of Moon.         */
static REAL moonage;    /* Age of Moon.                 */
static int  moon_symbol;/* Which Moon sprite to use.    */

/* Array of (address-type) sprite_ids for Moon symbols: */
static sprite_id moon_spr[NUM_SPR];

/********************************************************/
/*                 Function Prototypes                  */
/********************************************************/
static BOOL make_sprite_id(char *namestr, int i, sprite_id *idptr);
static BOOL make_sprite_name(char *namestr, int i, char *spr_name);
static void moon_buildfn(void);
static void moon_altaz(REAL *altptr, REAL *azimptr);
static void age(REAL *ageptr, int *symbolptr);
static void moon_altaz_any(observerstr *ptr, int hour, int min,
                          REAL *altptr, REAL *azimptr);
static void moon_radecfn(int id, observerstr *ob_ptr, int hour, int min,
                          REAL *traptr, REAL *tdecptr);
static void geocentric_rradec(double jul,
                          REAL *rptr, REAL *raptr, REAL *decptr);
static void topocentric_radec(observerstr *ob_ptr, REAL sid,
                          REAL r, REAL ra, REAL dec,
                          REAL *traptr, REAL *tdecptr);
static void geocentric_elatlong(double jul,
                                REAL *elatptr, REAL *elongptr, double *pptr);
static void moon_selectfn(selectfn_reasoncode reason);
static void selection_details(void);
static BOOL cul_calc(REAL altmin,
                REAL *altptr, REAL *azimptr, int *hourptr, int *minptr);
static BOOL rs_calc(BOOL whichcalc,
                    REAL *altptr, REAL *azimptr, int *hourptr, int *minptr);
static BOOL moon_displayfn(BOOL *enabptr);
static os_error *moon_plotfn(int x, int y, int id);
static void moon_infofn(int id);

/********************************************************/
/*                Initialisation Function               */
/********************************************************/
BOOL moon_initfn(int moduleno, modulestr *moon)
{
  int i;

  moduleid = moduleno;

  moon->buildfn  = moon_buildfn;
  moon->selectfn = moon_selectfn;
  moon->dispfn   = moon_displayfn;
  moon->infofn   = moon_infofn;
  moon->initial  = display_flag;
  moon->display_entry = DISP_NAME;
  moon->display_menu  = NULL;
  moon->select_entry  = SEL_NAME;
  moon->select_menu   = NULL;

/* Construct an array of address-type sprite_ids, each  */
/* for a different age of the Moon.  Quit if error.     */
  for (i = 0; i < NUM_SPR; i++)
    if (!make_sprite_id("moon", i, moon_spr + i)) return FALSE;

  return TRUE;
}

/*------------------------------------------------------*/
/*   Function to build an address-type sprite id corr-  */
/* esponding to the sprite name returned by the function*/
/* make_sprite_name. Returns TRUE if no errors detected.*/
/*------------------------------------------------------*/
static BOOL make_sprite_id(char *namestr, int i, sprite_id *idptr)
{
  char spr_name[SPRNAM_MAX]; /*For built-up sprite name.*/
  sprite_id nametype_id;

/* Construct sprite name.                               */
  if (!make_sprite_name(namestr, i, spr_name)) return FALSE;

/* Form a name-type sprite id using this name.          */
  nametype_id.s.name = spr_name;
  nametype_id.tag    = sprite_id_name;

/* Get address of sprite.  Quit if error.               */
  if (sprite_select_rp(resspr_area(),
                       &nametype_id,
                       &idptr->s.addr) != NULL) return FALSE;

/* Set tag to identify *idptr as an address-type id.    */
  idptr->tag = sprite_id_addr;

  return TRUE;
}

/*------------------------------------------------------*/
/* Function to make a sprite name from the given string */
/* namestr and the given integer i.  The name made is of*/
/* the form ssssnnn, where ssss is the first 9 chars of */
/* the given string (fewer if the string is shorter) and*/
/* nnn is the integer i expressed as 3 digits (000-999).*/
/* String pointed to by spr_name is assumed long enough.*/
/*          Return FALSE if error detected.             */
/*------------------------------------------------------*/
static BOOL make_sprite_name(char *namestr, int i, char *spr_name)
{
  int len;

  if (i<0 || i>999) return FALSE;

/* Copy at most 9 chars from namestr to spr_name.       */
  strncpy(spr_name, namestr, 9);

/* Add number.                                          */
  len = strlen(namestr);
  if (len > 9) len = 9;
  sprintf(spr_name+len, "%03i", i);

  return TRUE;
}


/********************************************************/
/*                 List-Building Function               */
/********************************************************/
static void moon_buildfn(void)
{
  plotobj the_moon;
  /* Bounding box of plotting symbol, relative to       */
  /* position of symbol:                                */
  wimp_box size = {-12, -12, 14, 14};

/* Choose to rebuild list regardless of whether or not  */
/* this module is enabled.                              */

/* Set up the one and only object owned by this module. */
  the_moon.id = 0;
  the_moon.module = moduleid;
  the_moon.plotfn = moon_plotfn;
  moon_altaz(&the_moon.alt, &the_moon.azim);
  the_moon.size = size;

/* Add this object to the plotting lists.               */
  addobj(the_moon, &horiz_id, &vert_id);

/* Also calculate age, and which moon symbol to use.    */
  age(&moonage, &moon_symbol);

  return;
}

/*------------------------------------------------------*/
/* Function to calculate the Moon's current alt & azim. */
/*------------------------------------------------------*/
static void moon_altaz(REAL *altptr, REAL *azimptr)
{
  REAL r;           /* Moon-Earth dist (in Earth radii).*/
  REAL ra, dec;     /* Geocentric RA and Dec of Moon.   */

/* Get the current geocentric RA and Dec of the Moon,   */
/* and its distance from the Earth.                     */
  geocentric_rradec(ob_data.jul, &r, &ra, &dec);

/* Convert to topocentric RA and Dec.                   */
  topocentric_radec(&ob_data, ob_data.sid, r, ra, dec, &moonra, &moondec);

/* Convert to altitude and azimuth.                     */
  radec_altaz(moonra, moondec, &ob_data, ob_data.sid, altptr, azimptr);

  return;
}

/*------------------------------------------------------*/
/*   Function to calculate Moon's alt & azim at any     */
/*    hour & min of the day specified in the given      */
/*   observerstr (which also specifies the location).   */
/*------------------------------------------------------*/
static void moon_altaz_any(observerstr *ptr, int hour, int min,
                          REAL *altptr, REAL *azimptr)
{
  REAL r;         /* Moon-Earth dist (in Earth radii).  */
  REAL ra, dec;   /* Geocentric RA and Dec of Moon.     */
  REAL tra, tdec; /* Topocentric RA and Dec of Moon.    */
  double jul;     /* Days since noon GMT 0/1/1900.      */
  REAL sid;       /* Local siderial time.               */

  jul = datime_julian(ptr, hour, min);
  sid = datime_siderial(ptr, jul, hour, min);

/* Get dist & geocentric RA and Dec.                    */
  geocentric_rradec(jul, &r, &ra, &dec);

/* Convert to topocentric RA and Dec.                   */
  topocentric_radec(ptr, sid, r, ra, dec, &tra, &tdec);

/* Convert to altitude and azimuth.                     */
  radec_altaz(tra, tdec, ptr, sid, altptr, azimptr);

  return;
}

/*------------------------------------------------------*/
/*   Function to calculate the topocentric RA & Dec of  */
/*  the Moon at a specified instant.  The parameters of */
/*   this function conform to the requirements of the   */
/*                  'Riset' functions.                  */
/*------------------------------------------------------*/
static void moon_radecfn(int id, observerstr *ob_ptr, int hour, int min,
                         REAL *traptr, REAL *tdecptr)
{
/* 'id' is not used as there is only one Moon.          */

  REAL r;         /* Moon-Earth dist (in Earth radii).  */
  REAL ra, dec;   /* Geocentric RA and Dec of Moon.     */
  double jul;     /* Days since noon GMT 0/1/1900.      */
  REAL sid;       /* Local siderial time.               */

  jul = datime_julian(ob_ptr, hour, min);
  sid = datime_siderial(ob_ptr, jul, hour, min);

/* Get dist & geocentric RA and Dec.                    */
  geocentric_rradec(jul, &r, &ra, &dec);

/* Convert to topocentric RA and Dec.                   */
  topocentric_radec(ob_ptr, sid, r, ra, dec, traptr, tdecptr);

  return;
}

/*------------------------------------------------------*/
/*  Function to calculate the geocentric RA and Dec of  */
/* the Moon, and its dist from Earth (in Earth radii).  */
/*------------------------------------------------------*/
static void geocentric_rradec(double jul,
                              REAL *rptr, REAL *raptr, REAL *decptr)
{
  double T;     /* Centuries since noon GMT 0/1/1900.   */
  double p;     /* Horizontal parallax.                 */
  double r;     /* Distancefrom Earth.                 */
  REAL elatit;  /* Geocentric ecliptic latitude.        */
  REAL elongit; /* Geocentric ecliptic longitude.       */

/* Get geocent. eclipt. lat & long, and horiz. parallax.*/
  geocentric_elatlong(jul, &elatit, &elongit, &p);

/* Convert to geocentric RA and Dec.                    */
  T = jul/36525.0;
  ecliptic_radec(elatit, elongit, T, raptr, decptr);

/* Calculate distance from Earth.                       */
  r = 1.0 / sin(p);
  *rptr = (REAL)r;

  return;
}

/*------------------------------------------------------*/
/*    Function to calculate the geocentric ecliptic     */
/*     latitude and longitude of the Moon, and the      */
/*  horizontal parallax, at the instant implied by jul  */
/* (No. of days since noon GMT on 0/1/1900).  Based on  */
/*   algorithm in NAO Technical Note No.46, Jan 1978,   */
/*            page 28.  Angles in radians.              */
/*------------------------------------------------------*/
static void geocentric_elatlong(double jul,
                                REAL *elatptr, REAL *elongptr, double *pptr)
{
  double M1, L, M, N, F, tN;
  double beta, lambda;

  M1 = DblCONV*fmod(358.48 +  0.985600*jul, 360.0);
  L  =         fmod(270.43 + 13.176397*jul, 360.0);
  M  = DblCONV*fmod(296.10 + 13.064992*jul, 360.0);
  N  = DblCONV*fmod(350.74 + 12.190749*jul, 360.0);
  F  = DblCONV*fmod( 11.25 + 13.229350*jul, 360.0);

  tN = 2.0*N;

/* Geocentric ecliptic latitude:                        */
  beta =  5.128 * sin(F)
        + 0.281 * sin(M  + F)
        + 0.278 * sin(M  - F)
        + 0.173 * sin(tN - F)
        - 0.055 * sin(M  - tN - F)
        - 0.046 * sin(M  - tN + F)
        + 0.033 * sin(tN + F);
  if (beta <    0.0) beta += 360.0;
  if (beta >= 360.0) beta  =   0.0;
  *elatptr = CONV * (REAL)beta;

/* Geocentric ecliptic longitude:                       */
  lambda =   L
           + 6.289 * sin(M)
           - 1.274 * sin(M  - tN)
           + 0.658 * sin(tN)
           + 0.214 * sin(2.0*M)
           - 0.186 * sin(M1)
           - 0.114 * sin(2.0*F)
           - 0.059 * sin(2.0*M  - tN)
           - 0.057 * sin(M1 + M - tN)
           + 0.053 * sin(M  + tN)
           - 0.046 * sin(M1 - tN)
           + 0.041 * sin(M  - M1)
           - 0.035 * sin(N)
           - 0.030 * sin(M  + M1);
  if (lambda <    0.0) lambda += 360.0;
  if (lambda >= 360.0) lambda -= 360.0;
  if (lambda <    0.0) lambda  =   0.0;
  *elongptr = CONV * (REAL)lambda;

/* Parallax:                                            */
  *pptr =   0.9507
          + 0.0518 * cos(M)
          + 0.0095 * cos(M - tN)
          + 0.0078 * cos(tN)
          + 0.0028 * cos(2.0*M);
  *pptr *= DblCONV;

  return;
}

/*------------------------------------------------------*/
/*     Function to convert geocentric RA and Dec to     */
/*    (approx) topocentric RA and Dec.  Formula from    */
/*          Astronomical Almanac 1990, p. D46.          */
/*------------------------------------------------------*/
static void topocentric_radec(observerstr *ob_ptr, REAL sid,
                              REAL r, REAL ra, REAL dec,
                              REAL *traptr, REAL *tdecptr)
{
  double x,  y,  z;  /* Geocentric rectangular coords.  */
  double x1, y1, z1; /* Topocentric rectangular coords. */
  double r1;
  double tra, tdec;
  double sintdec;

/* Get geocentric rectangular coords.                   */
  x = (double)r * cos((double)dec) * cos((double)ra);
  y = (double)r * cos((double)dec) * sin((double)ra);
  z = (double)r * sin((double)dec);

/* Derive topocentric rectangular coords.               */
  x1 = x - cos((double)ob_ptr->latit) * cos((double)sid);
  y1 = y - cos((double)ob_ptr->latit) * sin((double)sid);
  z1 = z - sin((double)ob_ptr->latit);
  r1 = sqrt(x1*x1 + y1*y1 + z1*z1);

/* Derive topocentric RA.                               */
  if (fabs(x1) < 1.0E-7  &&  fabs(y1) < 1.0E-7)
    tra = 0.0;
  else
  {
    tra = atan2(y1, x1);
    if (tra <        0.0) tra += DblTWO_PI;
    if (tra >= DblTWO_PI) tra  = 0.0;
  }
  *traptr = (REAL)tra;

/* Derive topocentric Dec.                              */
  sintdec = z1 / r1;
  if (sintdec < -1.0) sintdec = -1.0;
  if (sintdec >  1.0) sintdec =  1.0;
  tdec = asin(sintdec);
  *tdecptr = (REAL)tdec;

  return;
}

/*------------------------------------------------------*/
/*   Function to calculate the approximate age of the   */
/*        Moon, and decide which symbol to use.         */
/*------------------------------------------------------*/
static void age(REAL *ageptr, int *symbolptr)
{
  double T;    /* Centuries since noon GMT on 0/1/1900. */
  double a, b, c;
  double delta;

  T = ob_data.jul / 36525.0;
  b = DblCONV * fmod(296.1  + 477198.75 * T, 360.0);
  c = DblCONV * fmod(358.5  +  35999.04 * T, 360.0);
  a = 350.74 + 445267.14*T + 6.3*sin(b) - 1.9*sin(c);

  delta = fmod(a, 360.0);
  *ageptr = (REAL)0.082 * (REAL)delta;

/* Divide synodic month into NUM_SPR intervals, with    */
/* offset so that same symbol is used for just before & */
/* just after new moon.  Return a number in the range   */
/* 0 (nearest new moon) to NUM_SPR-1.                   */
  delta += 180.0 / (double)NUM_SPR;
  if (delta >= 360.0) delta -= 360.0;
  if (delta <    0.0) delta  =   0.0;
  delta /= 360.0;
  delta *= (double)NUM_SPR;
  *symbolptr = (int)delta;
  if (*symbolptr <        0) *symbolptr = 0;
  if (*symbolptr >= NUM_SPR) *symbolptr = NUM_SPR - 1;

  return;
}


/********************************************************/
/*              Object-Selecting Function               */
/********************************************************/
static void moon_selectfn(selectfn_reasoncode reason)
{
  switch (reason)
  {
    case new_selection:
      /* Only one Moon, so selection always succeeds.   */
      selection.id = 0;
      selection.selected_OK = TRUE;
    case window_selection:
      /* Selection has been made by clicking in a       */
      /* window.                                        */
    case recalculate_data:
      /* Observer details have changed, and plotting    */
      /* lists have been rebuilt.                       */
      /*                                                */
      /* Fill in the details of rising, setting etc.    */
      selection_details();
      break;

    case timeonly_recalculate:
      /* As recalculate_data, but only the time of day  */
      /* (and possibly the direction of view - this is  */
      /* is of no interest) have changed.               */
      /* This simplifies the calculation.               */
      selection.horiz_id = horiz_id;
      selection.vert_id  = vert_id;
      /* Decide if it can be seen now.                  */
      selection.now      = selection.horiz_id != NOWHERE || \
                           selection.vert_id  != NOWHERE;
      break;

    case sel_info_request:
      /* Write info into text buffer.   Use Info fn.    */
      moon_infofn(0);
      break;

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

    }

  return;
}

/*------------------------------------------------------*/
/*  Function to fill in the details of rising, setting  */
/*                   and culminating.                   */
/*------------------------------------------------------*/
static void selection_details(void)
{
  REAL rise_alt;  /*Altitude at calculated rising time. */
  REAL set_alt;   /*Altitude at calculated setting time.*/

  selection.horiz_id = horiz_id;
  selection.vert_id  = vert_id;
  selection.now      = selection.horiz_id != NOWHERE || \
                       selection.vert_id  != NOWHERE;

  selection.culminating = cul_calc(HORALT,
                              &selection.cul_alt,
                              &selection.cul_azim,
                              &selection.cul_hour,
                              &selection.cul_min);
  selection.rising      = rs_calc(RISECALC,
                              &rise_alt,
                              &selection.rise_azim,
                              &selection.rise_hour,
                              &selection.rise_min);
  selection.setting     = rs_calc(SETCALC,
                              &set_alt,
                              &selection.set_azim,
                              &selection.set_hour,
                              &selection.set_min);
  return;
}

/*------------------------------------------------------*/
/*      Function to derive details of culmination.      */
/*  If Moon culminates, fn finds alt, azim, hour & min  */
/* and returns TRUE.  Returns FALSE if no culmination.  */
/*------------------------------------------------------*/
static BOOL cul_calc(REAL altmin,
                REAL *altptr, REAL *azimptr, int *hourptr, int *minptr)
{
  BOOL ok;

/* Guess a time of day.  Take midday.                   */
  *hourptr = 12;
  *minptr  =  0;

/* Try to improve guessed time.                         */
  ok = riset_cul(
       &ob_data, MAX_ITER, CONVERGE, moon_radecfn, 0, hourptr, minptr);

/* Give up if time did not converge.                    */
  if (!ok) return FALSE;

/* Check that Moon would be higher than 'altmin'.       */
  moon_altaz_any(&ob_data, *hourptr, *minptr, altptr, azimptr);
  return (*altptr >= altmin);
}

/*------------------------------------------------------*/
/*   Function to derive details of rising or setting.   */
/* If phenomenon occurs, fn finds alt, azim, hour & min */
/*      and returns TRUE.  Returns FALSE otherwise.     */
/*------------------------------------------------------*/
static BOOL rs_calc(BOOL whichcalc,
                    REAL *altptr, REAL *azimptr, int *hourptr, int *minptr)
{
  BOOL ok;

/* Guess a time of day.  Take midday.                   */
  *hourptr = 12;
  *minptr  =  0;

/* Try to improve this guess.                           */
  ok = riset_riset(&ob_data, MAX_ITER, CONVERGE, moon_radecfn, 0,
                   whichcalc, HORALT, hourptr, minptr);

/* Give up if time did not converge to a valid value.   */
  if (!ok) return FALSE;

/* Check that Moon is indeed visible at this time.       */
  moon_altaz_any(&ob_data, *hourptr, *minptr, altptr, azimptr);
  return (*altptr > ZERO);
}


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

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

/* Data in plotting list is always kept up-to-date, so  */
/* no need to check validity or call list-building fn.  */

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


/********************************************************/
/*                   Plotting Function                  */
/********************************************************/
static os_error *moon_plotfn(int x, int y, int id)
{
  return sv_plotsprite(&moon_spr[moon_symbol], MODE_20, x, y, 5, 5);
}


/********************************************************/
/*                     Info Function                    */
/********************************************************/
static void moon_infofn(int id)
{
  char p_text[RADEC_TEXTLEN]; /*Text buff for RA & Dec.*/

/* Get RA and Dec in text form.                        */
  radec_text(moonra, moondec, p_text);

  sprintf(infoptr, "%s\n\nApprox age %.1f days\n%s",
    "The Moon",
    (double)moonage,
    p_text);

  return;
}
