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

#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 "sun.h"

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

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

#define SPR_NAME  "sun"     /* Name of sprite.          */

/*  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 ITER_MAX 5          /* Max no. of iterations.   */

/********************************************************/
/*                  Global Variables                    */
/********************************************************/
static BOOL display_flag = TRUE;   /* 'Enabled' flag.   */
static int moduleid;    /* Module ID.                   */
static int horiz_id;    /* Object's ID in Horiz window. */
static int vert_id;     /* Object's ID in Vert window.  */
static sprite_id spr_id;/* Identifies Sun sprite.       */
static REAL sunra;      /* Right Ascension of Sun.      */
static REAL sundec;     /* Declination of Sun.          */

/********************************************************/
/*                 Function Prototypes                  */
/********************************************************/
static void sun_buildfn(void);
static void sun_altaz(REAL *altptr, REAL *azimptr);
static void sun_altaz_any(observerstr *ptr, int hour, int min,
                          REAL *altptr, REAL *azimptr);
static void sun_radec(double jul, REAL *raptr, REAL *decptr);
static void sun_radecfn(int id, observerstr *ob_ptr, int hour, int min,
                        REAL *raptr, REAL *decptr);
static void sunlong(double T, REAL *elongptr);
static void sun_selectfn(selectfn_reasoncode reason);
static void selection_details(void);
static BOOL  cul(REAL altmin,
                 REAL *altptr, REAL *azimptr, int *hourptr, int *minptr);
static BOOL rise(REAL *altptr, REAL *azimptr, int *hourptr, int *minptr);
static BOOL  set(REAL *altptr, REAL *azimptr, int *hourptr, int *minptr);
static BOOL sun_displayfn(BOOL *enabptr);
static os_error *sun_plotfn(int x, int y, int id);
static void sun_infofn(int id);

/********************************************************/
/*                Initialisation Function               */
/********************************************************/
BOOL sun_initfn(int moduleno, modulestr *sun)
{
  sprite_id nametype_id;
  os_error *errptr;

  moduleid = moduleno;

  sun->buildfn  = sun_buildfn;
  sun->selectfn = sun_selectfn;
  sun->dispfn   = sun_displayfn;
  sun->infofn   = sun_infofn;
  sun->initial  = display_flag;
  sun->display_entry = DISP_NAME;
  sun->display_menu  = NULL;
  sun->select_entry  = SEL_NAME;
  sun->select_menu   = NULL;

/* Initialise sprite identifier.                        */
/* First build a name-type sprite id.                   */
  nametype_id.s.name = SPR_NAME;
  nametype_id.tag    = sprite_id_name;
/* Then get the sprite's address & 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;
}


/********************************************************/
/*                 List-Building Function               */
/********************************************************/
static void sun_buildfn(void)
{
  plotobj the_sun;
  /* Bounding box of plotting symbol, relative to       */
  /* position of symbol:                                */
  static 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_sun.id = 0;
  the_sun.module = moduleid;
  the_sun.plotfn = sun_plotfn;
  sun_altaz(&the_sun.alt, &the_sun.azim);
  the_sun.size = size;

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

  return;
}

/*------------------------------------------------------*/
/* Function to calculate the current solar alt & azim.  */
/*------------------------------------------------------*/
static void sun_altaz(REAL *altptr, REAL *azimptr)
{

/* Get the current RA and Dec of the Sun.               */
  sun_radec(ob_data.jul, &sunra, &sundec);

/* Convert these to altitude and azimuth.               */
  radec_altaz(sunra, sundec, &ob_data, ob_data.sid, altptr, azimptr);

  return;
}

/*------------------------------------------------------*/
/*  Function to calculate the solar alt & azim at any   */
/*     hour & min of the day specified in the given     */
/*   observerstr (which also specifies the location).   */
/*------------------------------------------------------*/
static void sun_altaz_any(observerstr *ptr, int hour, int min,
                          REAL *altptr, REAL *azimptr)
{
  REAL ra, dec;   /* RA and Dec of Sun.                 */
  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 the RA and Dec of the Sun at that time.          */
  sun_radec(jul, &ra, &dec);

/* Convert these to altitude and azimuth.               */
  radec_altaz(ra, dec, ptr, sid, altptr, azimptr);

  return;
}

/*------------------------------------------------------*/
/*  Function to calculate the RA & Dec of the Sun at a  */
/*                  specified instant.                  */
/*------------------------------------------------------*/
static void sun_radec(double jul, REAL *raptr, REAL *decptr)
{
  double T;        /* Julian centuries from 1900.       */
  REAL elongit;    /* Ecliptic longitude of Sun.        */

/* Find no. of Julian centuries from noon GMT 0/1/1900. */
  T = jul/36525.0;

/* Find ecliptic longitude of Sun at specified instant. */
  sunlong(T, &elongit);

/* Convert to RA & Dec. Ecliptic latitude is always 0.0 */
  ecliptic_radec(ZERO, elongit, T, raptr, decptr);

  return;
}

/*------------------------------------------------------*/
/*  Function to calculate the RA & Dec of the Sun at a  */
/* specified instant.  Same as sun_radec, but arguments */
/* conform to the requirements of the 'Riset' functions.*/
/*------------------------------------------------------*/
static void sun_radecfn(int id, observerstr *ob_ptr, int hour, int min,
                        REAL *raptr, REAL *decptr)
{
/* 'id' is not used as there is only one Sun.           */

  sun_radec(datime_julian(ob_ptr, hour, min), raptr, decptr);

  return;
}

/*------------------------------------------------------*/
/* Function to calculate the ecliptic longitude of the  */
/*    Sun at the instant implied by T (No. of Julian    */
/*        centuries since noon GMT on 0/1/1900).        */
/* Based on algorithm in NAO Technical Note No.46, Jan  */
/*        1978, page 21.      Result in radians.        */
/*------------------------------------------------------*/
static void sunlong(double T, REAL *elongptr)
{
  double L;         /* Mean longitude of Sun.           */
  double M;         /* Mean anomaly.                    */
  double e;         /* Eccentricity (not used here).    */
  double lambda;    /* Ecliptic longitude of Sun.       */

/* Get elements of sun's "orbit".                       */
  ecliptic_sunorbit(T, &L, &M, &e);

/* Get ecliptic longitude.  Keep units consistent.      */
  lambda  = (1.919 - 0.005*T)*sin(M) + 0.020*sin(2.0*M);
  lambda *= DblCONV;
  lambda += L;

  *elongptr = (REAL)lambda;
  while(*elongptr <  ZERO  ) *elongptr += TWO_PI;
  while(*elongptr >= TWO_PI) *elongptr -= TWO_PI;
  if   (*elongptr <  ZERO  ) *elongptr  = ZERO;

  return;
}

/********************************************************/
/*              Object-Selecting Function               */
/********************************************************/
static void sun_selectfn(selectfn_reasoncode reason)
{
  switch (reason)
  {
    case new_selection:
      /* Only one Sun, 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.    */
      sun_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(HORALT,
                               &selection.cul_alt,
                               &selection.cul_azim,
                               &selection.cul_hour,
                               &selection.cul_min);
  selection.rising      = rise(&rise_alt,
                               &selection.rise_azim,
                               &selection.rise_hour,
                               &selection.rise_min);
  selection.setting     =  set(&set_alt,
                               &selection.set_azim,
                               &selection.set_hour,
                               &selection.set_min);
  return;
}

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

/* Guess a time of day.  Take true local midday.        */
  time_of_day = (REAL)12.00 + ob_data.longit/(REAL)15.0/CONV
                + ob_data.offset;
  if (time_of_day  <        ZERO) time_of_day += (REAL)24.0;
  if (time_of_day  >= (REAL)24.0) time_of_day -= (REAL)24.0;

/* Convert this to hours and minutes.                   */
  datime_hourmin(time_of_day, hourptr, minptr);

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

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

/* Check that sun is higher than 'altmin' at this time. */
  sun_altaz_any(&ob_data, *hourptr, *minptr, altptr, azimptr);
  return (*altptr >= altmin);
}

/*------------------------------------------------------*/
/*        Function to derive details of rising.         */
/*   If Sun rises, fn finds alt, azim, hour & min and   */
/*  returns  TRUE.  Returns FALSE if sun does not rise. */
/*------------------------------------------------------*/
static BOOL rise(REAL *altptr, REAL *azimptr, int *hourptr, int *minptr)
{
  REAL time_of_day;
  BOOL ok;

/* Guess a time of day.  Take true local 06:00.         */
  time_of_day = (REAL)6.00 + ob_data.longit/(REAL)15.0/CONV
                + ob_data.offset;
  if (time_of_day  <        ZERO) time_of_day += (REAL)24.0;
  if (time_of_day  >= (REAL)24.0) time_of_day -= (REAL)24.0;

/* Convert this to hours and minutes.                   */
  datime_hourmin(time_of_day, hourptr, minptr);

/* Try to improve this guess.                           */
  ok = riset_riset(&ob_data, ITER_MAX, CONVERGE, sun_radecfn, 0,
                   RISECALC, HORALT, hourptr, minptr);

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

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

/*------------------------------------------------------*/
/*       Function to derive details of setting.         */
/*   If Sun sets, fn finds alt, azim, hour & min and    */
/*  returns  TRUE.  Returns FALSE if sun does not set.  */
/*------------------------------------------------------*/

static BOOL set(REAL *altptr, REAL *azimptr, int *hourptr, int *minptr)
{
  REAL time_of_day;
  BOOL ok;

/* Guess a time of day.  Take true local 18:00.         */
  time_of_day = (REAL)18.00 + ob_data.longit/(REAL)15.0/CONV
                + ob_data.offset;
  if (time_of_day  <        ZERO) time_of_day += (REAL)24.0;
  if (time_of_day  >= (REAL)24.0) time_of_day -= (REAL)24.0;

/* Convert this to hours and minutes.                   */
  datime_hourmin(time_of_day, hourptr, minptr);

/* Try to improve this guess.                           */
  ok = riset_riset(&ob_data, ITER_MAX, CONVERGE, sun_radecfn, 0,
                   SETCALC, HORALT, hourptr, minptr);

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

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

/********************************************************/
/*                   Display Function                   */
/********************************************************/
static BOOL sun_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 *sun_plotfn(int x, int y, int id)
{
  return sv_plotsprite(&spr_id, MODE_20, x, y, 5, 5);
}


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

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

  sprintf(infoptr, "%s\n\n%s",
    "The Sun",
    p_text);

  return;
}
