/********************************************************/
/*              Markers Module for SkyView              */
/*                                                      */
/*                  (c)1992 N P Hawkes                  */
/*                                                      */
/*    Displays markers at regular RA & Dec intervals    */
/********************************************************/

#include "menu.h"
#include "dbox.h"
#include "bbc.h"
#include "wimpt.h"
#include "dbox.h"
#include "string.h"
#include "event.h"

#include "sv_header.h"
#include "markers.h"
#include "radec.h"

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

/********************************************************/
/*                     Constants                        */
/********************************************************/
#define DISP_NAME "Markers"    /* Entry in Display menu.*/
#define SEL_NAME  "Marker..."  /* Entry in Select menu. */
#define SELBOX_NAME "MarkersSel" /* Name of Sel dbox.   */

#define HORALT (REAL)0.0044      /* Altitude of horizon.*/

/* No. of Declination values (starting at -90 deg.):    */
#define NDEC 19

/*No. of RA values at each Dec value (starting at 0 hr):*/
static int nra[NDEC] =
  {1, 12, 12, 12, 12, 12, 12, 12, 12, 24,
      12, 12, 12, 12, 12, 12, 12, 12, 1};

/* Constants relating to Selection dialogue box.        */
enum {
  box_cancel = -1,  /* Response cancelling the dbox.    */
  sel_ok     =  0,  /* OK button.                       */
  sel_cancel,       /* Cancel button.                   */
  dec_dn,           /* Button to decrease declination.  */
  dec_up,           /* Button to increase declination.  */
  dec_field,        /* Field No. for declination.       */
  ra_dn,            /* Button to decrease rt ascension. */
  ra_up,            /* Button to increase rt ascension. */
  ra_field          /* Field No. for rt ascension.      */
};

/********************************************************/
/*                New types of variable.                */
/********************************************************/
/* Structure describing one marker.                     */
typedef struct {
  REAL ra;          /* Right ascension (radians).       */
  REAL dec;         /* Declination (radians).           */
  int  horiz_id;    /* id in Horiz window.              */
  int  vert_id;     /* id in Vert window.               */
  int  hi;          /* Markers in range lo (inclusive)  */
  int  lo;          /* to hi (exclusive) all have       */
                    /* declination 'dec'.               */
} markerstr;

/********************************************************/
/*                  Global Variables                    */
/********************************************************/
static BOOL display_flag = FALSE;  /* 'Enabled' flag.   */
static BOOL list_valid   = FALSE;  /* TRUE if list valid*/
static int moduleid;               /* Module ID.        */

static int marker_count = 0;   /* Total no. of markers. */
static markerstr *marker_data; /* Ptr to main data store*/

static dbox d_sel;            /* Selection dbox.        */
static int sel_id = 97;       /* ID of most recent sel- */
                              /* ection made via dbox.  */

/********************************************************/
/*                 Function Prototypes                  */
/********************************************************/
static void markers_buildfn(void);
static BOOL markers_displayfn(BOOL *enabptr);
static os_error *markers_plotfn(int x, int y, int id);
static void markers_infofn(int id);
static void markers_selectfn(selectfn_reasoncode reason);
static int  selectionbox(void);
static void selection_details(void);
static void selbox_load(int id);
static int selbox_buttons(int start_id);
static int find_lower_dec(int old_id, REAL target);
static int find_higher_dec(int old_id, REAL target);
static int find_lower_ra(int old_id);
static int find_higher_ra(int old_id);
static int ra_search(int lo, int hi, REAL target);

/********************************************************/
/*                Initialisation Function               */
/********************************************************/
BOOL markers_initfn(int moduleno, modulestr *markers)
{
  int i, j;
  int first;
  REAL ddec;      /* Interval of Declination (radians). */
  REAL dra;       /* Interval of Rt Ascension (radians).*/
  int marker_id    = 0;  /* id number of marker.        */

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


/*        Allocate memory for main data store.          */

/* Calculate how many markers there are.                */
  for (i=0; i<NDEC; i++)
    marker_count += nra[i];
/* Allocate memory.  Quit if allocation fails.          */
  marker_data = calloc(marker_count, sizeof(markerstr));
  if (marker_data == NULL) return FALSE;

/*            Generate the data array.                  */

/* Calculate declination interval in radians.           */
  if (NDEC > 1)
    ddec = PI/(NDEC - 1);

/* Calculate data for all markers at each dec. value.   */
  for (i=0; i<NDEC; i++)
  {
  /* Remember first id number at this dec.              */
    first = marker_id;
  /* Find interval of right ascension.                  */
    dra = (REAL)2.0*PI/nra[i];
  /* Deal with each ra value in turn.                   */
    for (j=0; j<nra[i]; j++)
    {
      marker_data[marker_id].ra  = j * dra;
      marker_data[marker_id].dec = i * ddec - PIby2;
      marker_data[marker_id].lo = first;
      marker_data[marker_id].hi = first + nra[i];
      marker_id++;
    }
  }


/* Fill in the fields of the modulestr.                 */
  markers->buildfn  = markers_buildfn;
  markers->selectfn = markers_selectfn;
  markers->dispfn   = markers_displayfn;
  markers->infofn   = markers_infofn;
  markers->initial  = display_flag;
  markers->display_entry = DISP_NAME;
  markers->display_menu  = NULL;
  markers->select_entry  = SEL_NAME;
  markers->select_menu   = NULL;

  return TRUE;
}

/********************************************************/
/*                 List-Building Function               */
/********************************************************/
static void markers_buildfn(void)
{
  int i;
  plotobj marker;

  /* Bounding box of plotting symbol, relative to       */
  /* position of symbol:                                */
  static wimp_box size = {-10, -10, 10, 10};

/* If module is currently disabled, ignore call to list-*/
/* building function, and set flag to indicate that     */
/* module does not have valid data in the plotting list.*/
  if (!display_flag)
  {
    list_valid = FALSE;
    return;
  }

/* If module is currently enabled, build plotting list  */
/* and set flag to indicate module has valid data.      */
  list_valid = TRUE;

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

    /* Build plotobj.                                   */
    marker.id = i;
    marker.module = moduleid;
    marker.plotfn  = markers_plotfn;
    radec_altaz(marker_data[i].ra, marker_data[i].dec, &ob_data, ob_data.sid,
      &marker.alt, &marker.azim);
    marker.size = size;

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

  }

  return;
}


  
/********************************************************/
/*              Object-Selecting Function               */
/********************************************************/
static void markers_selectfn(selectfn_reasoncode reason)
{
  int marker_id;

  switch (reason)
  {
    case new_selection:
      /* Using dialogue box, determine which marker is  */
      /* to be selected.   A return of -1 indicates     */
      /* dialogue was cancelled.                        */
      marker_id = selectionbox();
      if (marker_id == -1)
      {
        selection.selected_OK = FALSE;
        return;
      }
      /* Valid selection has been made.                 */
      /* Remember id of selected marker for next time,  */
      /* and give the id number to the main program.    */
      sel_id       = marker_id;
      selection.id = marker_id;
      /*  Flag the selection as successful.             */
      selection.selected_OK = TRUE;
      /* Fill in Selection details for requested marker.*/
      selection_details();
      break;

    case window_selection:
      /* A selection has been made by clicking in a     */
      /* window.                                        */
    case recalculate_data:
      /* Observer details have changed, and plotting    */
      /* lists have been rebuilt.                       */
      /*                                                */
      /* In both cases, the ID of the marker is already */
      /* in selection.id                                */
      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.               */
      marker_id = selection.id;
      selection.horiz_id    = marker_data[marker_id].horiz_id;
      selection.vert_id     = marker_data[marker_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.                   */
      {
      REAL rah;    /* Right Ascension in hours.         */
      REAL decd;   /* Declination in degrees.           */

      marker_id = selection.id;

      rah  = marker_data[marker_id].ra * (REAL)12.0 / PI;
      decd = marker_data[marker_id].dec / CONV;

      sprintf(infoptr,"%s%s%.1f%s%s%.1f%s",
                  "Marker\n",
                  "RA ", (double)rah, " hours\n",
                  "Dec ",(double)decd,"");
      }
      break;

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

    }

  return;
}

/*------------------------------------------------------*/
/* Function to obtain id number of marker to be selected*/
/*             by means of a dialogue box.              */
/*         Returns -1 if dialogue is cancelled.         */
/*------------------------------------------------------*/
static int selectionbox(void)
{
  int result;              /*Result of dialogue.        */

/* Ensure menu has gone, create dbox, load it with info */
/* and put it on display.  (If not already done.)       */
  if (!dbox_persisting)
  {
    event_clear_current_menu();
    d_sel = dbox_new(SELBOX_NAME);
    if (d_sel == NULL) return -1;
    selbox_load(sel_id);
    dbox_show(d_sel);
  }

/* Deal with button clicks until user clicks OK, or     */
/* clicks Cancel with left hand mouse button, or clicks */
/* outside dbox. Result is chosen id, or -1 if cancelled*/
  result = selbox_buttons(sel_id);

/* Delete dbox if it is not required to persist.        */
  if (!dbox_persisting) dbox_dispose(&d_sel);

/* Return the chosen value.                             */
  return result;
}

/*------------------------------------------------------*/
/*   Function to load info from marker No. 'id' into    */
/*                    selection dbox                    */
/*------------------------------------------------------*/
static void selbox_load(int id)
{
 
  REAL rah;    /* Right Ascension in hours.             */
  REAL decd;   /* Declination in degrees.               */
  char numbuf[32];

  rah  = marker_data[id].ra * (REAL)12.0 / PI;
  decd = marker_data[id].dec / CONV;

/* Get RA of marker into string form.                   */
  sprintf(numbuf, "%.1f hours", (double)rah);
/* Write RA into ra field of dbox.                      */
  dbox_setfield(d_sel, ra_field, numbuf);

/* Get Dec of marker into string form.                  */
  sprintf(numbuf, "%+.1f", (double)decd);
/* Write Dec into dec field of dbox.                    */
  dbox_setfield(d_sel, dec_field, numbuf);

  return;
}

/*------------------------------------------------------*/
/* Function to deal with button clicks in Selection box.*/
/*------------------------------------------------------*/
static int selbox_buttons(int start_id)
{
/* start_id is ID number on show initially.             */

/* new_id is ID number currently on show.               */
  int new_id = start_id;

  int response;    /* Latest button No. clicked by user.*/
  REAL ra_target;  /* RA of marker is to be as close to */
                   /* this as possible.                 */

  ra_target = marker_data[start_id].ra;

/* Loop until user clicks on OK, or on Cancel with left-*/
/* hand mouse button, or closes dbox with a 'harmful    */
/* event' (eg clicking outside dbox).                   */
  for (;;)
  {

  /* Get field number of latest click.                  */
    response = dbox_fillin(d_sel);

  /* Take appropriate action.                           */
    switch (response) {

      case box_cancel:
      /* dbox cancelled.  Close it and return -1.       */
        dbox_persisting = FALSE;
        return -1;

      case sel_ok:
      /* Return current id number.  Retain dbox if click*/
      /* was with Adjust.                               */
        dbox_persisting = dbox_persist();
        return new_id;

      case sel_cancel:
      /* If normal click, close dbox and return -1.     */
      /* If Adjust, reload initial id and keep looping. */
        if (dbox_persist())
        {
          new_id = start_id;
          ra_target = marker_data[start_id].ra;
          selbox_load(new_id);
        }
        else
        {
          dbox_persisting = FALSE;
          return -1;
        }
        break;

      case dec_dn:
      /* Decrease declination.  Find marker with next   */
      /* lower dec, and RA as close to ra_target as poss*/
        new_id = find_lower_dec(new_id, ra_target);
      /* Load new info into dbox.                       */
        selbox_load(new_id);
        break;

      case dec_up:
      /* Increase declination.  Find marker with next   */
      /* higher dec, and RA as close to ra_target as    */
      /* possible.                                      */
        new_id = find_higher_dec(new_id, ra_target);
      /* Load new info into dbox.                       */
        selbox_load(new_id);
        break;

      case ra_dn:
      /* Decrease Right Ascension.                      */
        new_id = find_lower_ra(new_id);
      /* Update ra_target.                              */
        ra_target = marker_data[new_id].ra;
      /* Load new info into dbox.                       */
        selbox_load(new_id);
        break;

      case ra_up:
      /* Increase Right Ascension.                      */
        new_id = find_higher_ra(new_id);
      /* Update ra_target.                              */
        ra_target = marker_data[new_id].ra;
      /* Load new info into dbox.                       */
        selbox_load(new_id);
        break;

    }
  }
}

/*------------------------------------------------------*/
/*  Functions to find new marker id which most closely  */
/*        fits the change requested by the user.        */
/*------------------------------------------------------*/
static int find_lower_dec(int old_id, REAL target)
{
/* Search for id having 'dec' one increment lower than  */
/* current marker, and 'ra' as close as poss. to target.*/

  int old_lo; /* First id with the current value of dec.*/
  int dum_id; /* Used to obtain search range.           */
  int new_lo; /* Start of range to be searched for ra.  */
  int new_hi; /* End of search range (exclusive).       */

  old_lo = marker_data[old_id].lo;

/*      Establish range of ids to be searched.          */
/* Obtain an id which has the desired declination.      */
/* Cycle round to top of dec range if necessary.        */
  dum_id = (old_lo > 0 ? old_lo - 1 : marker_count - 1);
/* Use this id to get range of ids with desired dec.    */
  new_lo = marker_data[dum_id].lo;
  new_hi = marker_data[dum_id].hi;

/* Return id within this range which has closest ra.    */
  return ra_search(new_lo, new_hi, target);
}

static int ra_search(int lo, int hi, REAL target)
{
  REAL min_error = (REAL)2.0*PI;
  REAL error;
  int  id;
  int  nearest = 0;

  for (id=lo; id<hi; id++)
  {
    error = marker_data[id].ra - target;
    if (error < (REAL)0.0) error = -error;
    if (error < min_error)
    {
      min_error = error;
      nearest = id;
    }
  }
  return nearest;
}

static int find_higher_dec(int old_id, REAL target)
{
/* Search for id having 'dec' one increment higher than */
/* current marker, and 'ra' as close as poss. to target.*/

  int old_hi; /* Last id with the current value of dec. */
  int dum_id; /* Used to obtain search range.           */
  int new_lo; /* Start of range to be searched for ra.  */
  int new_hi; /* End of search range (exclusive).       */

  old_hi = marker_data[old_id].hi;

/*      Establish range of ids to be searched.          */
/* Obtain an id which has the desired declination.      */
/* Cycle round to botttom of dec range if necessary.    */
  dum_id = (old_hi >= marker_count ? 0 : old_hi);
/* Use this id to get range of ids with desired dec.    */
  new_lo = marker_data[dum_id].lo;
  new_hi = marker_data[dum_id].hi;

/* Return id within this range which has closest ra.    */
  return ra_search(new_lo, new_hi, target);

}

static int find_lower_ra(int old_id)
{
  int new_id = old_id - 1;

  if (new_id < marker_data[old_id].lo)
    new_id = marker_data[old_id].hi - 1;

  return new_id;
}

static int find_higher_ra(int old_id)
{
  int new_id = old_id + 1;

  if (new_id >= marker_data[old_id].hi)
    new_id = marker_data[old_id].lo;

  return new_id;
}


/*------------------------------------------------------*/
/*  Function to fill in selection details of selected   */
/*   marker (viewing directions and times for rising,   */
/*             setting, culminating etc).               */
/*------------------------------------------------------*/
static void selection_details(void)
{
  double ch;    /* cos of hour angle at horizon.        */
  BOOL riset;   /* Whether object rises & sets or not.  */
  BOOL culmin;  /* Whether object culminates or not.    */

/* ID number of marker is in selection.id               */
  int id = selection.id;

/* Record window IDs of selected marker.                */
  selection.horiz_id    = marker_data[id].horiz_id;
  selection.vert_id     = marker_data[id].vert_id;

/* Find out if it can be seen now.                      */
  selection.now         = selection.horiz_id != NOWHERE || \
                          selection.vert_id  != NOWHERE;

/* Find out if marker rises, sets or culminates today.  */
/* (After setting effective altitude of horizon.)       */
  radec_sethoriz(HORALT);

  radec_phenomena(marker_data[id].ra, marker_data[id].dec, &ob_data,
                  &ch, &riset, &culmin);
  selection.rising      = riset;
  selection.setting     = riset;
  selection.culminating = culmin;

/* Fill in details for each phenomenon which occurs.    */

  if (selection.rising)
    radec_rise_details(
        marker_data[id].ra, marker_data[id].dec, &ob_data, ch,
        &selection.rise_azim,
        &selection.rise_hour,
        &selection.rise_min);

  if (selection.setting)
    radec_set_details(
        marker_data[id].ra, marker_data[id].dec, &ob_data, ch,
        &selection.set_azim,
        &selection.set_hour,
        &selection.set_min);

  if (selection.culminating)
    radec_cul_details(
        marker_data[id].ra, marker_data[id].dec, &ob_data,
        &selection.cul_alt,
        &selection.cul_azim,
        &selection.cul_hour,
        &selection.cul_min);

  return;
}

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

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

/* If module is now enabled, but does not have valid    */
/* data in the plotting list, call the list-building fn.*/
  if (display_flag && !list_valid)
    markers_buildfn();

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


/********************************************************/
/*                   Plotting Function                  */
/********************************************************/
static os_error *markers_plotfn(int x, int y, int id)
{
  os_error *errptr;

/* Set foreground graphics colour to Wimp colour 10     */
  errptr = sv_setcolour(10);
  if (errptr != NULL) return errptr;

/* Draw a suitable symbol at the given coordinates.     */
  errptr = bbc_move(x-8, y);
  if (errptr != NULL) return errptr;
  errptr = bbc_draw(x+8, y);
  if (errptr != NULL) return errptr;
  errptr = bbc_move(x, y-8);
  if (errptr != NULL) return errptr;
  
  return   bbc_draw(x, y+8);
}


/********************************************************/
/*                     Info Function                    */
/********************************************************/
static void markers_infofn(int id)
{
  REAL rah;    /* Right Ascension in hours.           */
  REAL decd;   /* Declination in degrees.             */

  rah  = marker_data[id].ra * (REAL)12.0 / PI;
  decd = marker_data[id].dec / CONV;

  sprintf(infoptr,"%s%s%.1f%s%s%.1f%s",
                  "Marker\n",
                  "RA ", (double)rah, " hours\n",
                  "Dec ",(double)decd,"");

  return;
}
