/********************************************************/
/*              UserObj Module for SkyView              */
/*                                                      */
/*                  (c)1992 N P Hawkes                  */
/*                                                      */
/*      Displays User Object at specified RA & Dec      */
/********************************************************/

#include "menu.h"
#include "dbox.h"
#include "bbc.h"
#include "wimpt.h"
#include "dbox.h"
#include "string.h"
#include "event.h"
#include "res.h"
#include "resspr.h"
#include "werr.h"
#include "os.h"

#include "sv_header.h"
#include "userobj.h"
#include "radec.h"

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

/********************************************************/
/*                     Constants                        */
/********************************************************/
#define DISP_NAME  ">User obj." /*Entry in Display menu.*/
#define SEL_NAME   " User obj." /*Entry in Select menu. */
#define FILE_NAME    "UserData" /* Name of disc file.   */
#define DISPBOX_NAME "UserDisp" /* Name of Disp dbox.   */
#define SPRITE_NAME  "userobj"  /*Name of UserObj Sprite*/

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

/* Length of one line of description (inc. \0 at end):  */
#define USEROBJ_TEXTLEN 31

/* Constants relating to Display dialogue box.          */
enum {
  box_destroy = -1, /* Response cancelling the dbox.    */
  ok_butt     =  0, /* OK button.                       */
  cancel_butt,      /* Cancel button.                   */
  show_butt,        /* Show Defaults button.            */
  set_butt,         /* Set Defaults button.             */
  rah_field,        /* Field for hour part of RA.       */
  ram_field,        /* Field for minute part of RA.     */
  decd_field,       /* Field for degree part of Dec.    */
  decm_field,       /* Field for minute part of Dec.    */
  ns_field,         /* Field for 'N' or 'S'.            */
  ns_butt,          /* Button to toggle between N & S.  */
  rah_tu  = 10,     /* Increment tens in rah_field.     */
  rah_td,           /* Decrement tens in rah_field.     */
  rah_uu,           /* Increment units in rah_field.    */
  rah_ud,           /* Decrement units in rah_field.    */
  ram_tu  = 14,     /* Increment tens in ram_field.     */
  ram_td,           /* Decrement tens in ram_field.     */
  ram_uu,           /* Increment units in ram_field.    */
  ram_ud,           /* Decrement units in ram_field.    */
  decd_tu = 18,     /* Increment tens in decd_field.    */
  decd_td,          /* Decrement tens in decd_field.    */
  decd_uu,          /* Increment units in decd_field.   */
  decd_ud,          /* Decrement units in decd_field.   */
  decm_tu = 22,     /* Increment tens in decm_field.    */
  decm_td,          /* Decrement tens in decm_field.    */
  decm_uu,          /* Increment units in decm_field.   */
  decm_ud,          /* Decrement units in decm_field.   */
  text1_field = 26, /* Field for first line of text.    */
  text2_field,      /* Field for 2nd line of text.      */
  text3_field       /* Field for 3rd line of text.      */
};

/********************************************************/
/*                New types of variable.                */
/********************************************************/
/* Structure describing data on show in Display dbox.   */
typedef struct {
  int  rah;         /* Hour part of Right Ascension.    */
  int  ram;         /* Minute part of Right Ascension.  */
  int  decd;        /* Degree part of Declination.      */
  int  decm;        /* Minute part of Declination.      */
  BOOL isn;         /* TRUE if Dec is North.            */
  char text1[USEROBJ_TEXTLEN];   /* First line of text. */
  char text2[USEROBJ_TEXTLEN];   /* 2nd line of text.   */
  char text3[USEROBJ_TEXTLEN];   /* 3rd line of text.   */
} userobjstr;

/********************************************************/
/*                  Global Variables                    */
/********************************************************/
static BOOL display_flag = FALSE;  /* 'Enabled' flag.   */
/* Flag indicating whether a Rebuild Request is pending:*/
static BOOL request_pending  = FALSE;
static int moduleid;               /* Module ID.        */
static sprite_id spr_id;           /* For Userobj sprite*/

static dbox d_disp;                /* Display dbox.     */
static userobjstr current_data = { /* Current data.     */
  0,
  0,
  0,
  0,
  TRUE,
  "",
  "<Untitled>",
  ""
};

static REAL ra;         /* Current RA.                  */
static REAL dec;        /* Current Dec.                 */

static int  horiz_id;   /* Current ID in Horiz window.  */
static int  vert_id;    /* Current ID in Vert window.   */

static int addon[4] =   /* For incrementing / decrement-*/
{ 10,                   /* ing numbers in Display dbox. */
 -10,
   1,
  -1 };

/********************************************************/
/*                 Function Prototypes                  */
/********************************************************/
static BOOL read_discfile(userobjstr *ptr);
static void userobj_radec(userobjstr *ptr, REAL *raptr, REAL *decptr);
static BOOL write_discfile(userobjstr *ptr);
static void userobj_buildfn(void);
static BOOL userobj_displayfn(BOOL *enabptr);
static void load_dispbox(userobjstr *ptr);
static void read_dispbox(userobjstr *ptr);
static BOOL dispbox_buttons(void);
static BOOL copy_data(userobjstr *ptr1, userobjstr *ptr2);
static void dispbox_databutts(int response);
static os_error *userobj_plotfn(int x, int y, int id);
static void userobj_infofn(int id);
static void userobj_selectfn(selectfn_reasoncode reason);
static void selection_details(void);

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

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

/* Attempt to read default settings from disc.  If      */
/* error, just carry on with hard-wired settings.       */
  read_discfile(&current_data);
/* Set the RA and Dec to correspond with these values.  */
  userobj_radec(&current_data, &ra, &dec);

/* Initialise sprite identifier.                        */
/* First build a name-type sprite id.                   */
  nametype_id.s.name = SPRITE_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;

/* Fill in the fields of the modulestr.                 */
  userobj->buildfn  = userobj_buildfn;
  userobj->selectfn = userobj_selectfn;
  userobj->dispfn   = userobj_displayfn;
  userobj->infofn   = userobj_infofn;
  userobj->initial  = display_flag;
  userobj->display_entry = DISP_NAME;
  userobj->display_menu  = NULL;
  userobj->select_entry  = SEL_NAME;
  userobj->select_menu   = NULL;

  return TRUE;
}

/*------------------------------------------------------*/
/*  Function to read default values of RA, Dec and text */
/*  from disc file.  Returns TRUE if data read OK.  If  */
/*    error, returns FALSE and leaves *ptr unchanged.   */
/*------------------------------------------------------*/
static BOOL read_discfile(userobjstr *ptr)
{
  userobjstr tempstr[1];
  FILE *fileptr;
  size_t numread;

/* Open disc file for reading binary.                   */
  fileptr = res_openfile(FILE_NAME, "rb");
  if (fileptr == NULL) return FALSE;

/* Try to read one userobjstr from file.                */
  numread = fread((void *)tempstr, sizeof(userobjstr), 1, fileptr);

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

/* If attempt to read failed, return FALSE now.         */
  if (numread != 1) return FALSE;

/* All OK, so transfer data to *ptr and return TRUE.    */
  *ptr = tempstr[0];
  return TRUE;
}

/*------------------------------------------------------*/
/* Function to write to disc the values of RA, Dec and  */
/*   text pointed to by ptr.  Returns FALSE if error.   */
/*------------------------------------------------------*/
static BOOL write_discfile(userobjstr *ptr)
{
  userobjstr tempstr[1];
  FILE *fileptr;
  size_t numwritten;

/* Copy data into array variable.                       */
  tempstr[0] = *ptr;

/* Open disc file for writing binary.                   */
  fileptr = res_openfile(FILE_NAME, "wb");
  if (fileptr == NULL) return FALSE;

/* Try to write one userobjstr to file.                 */
  numwritten = fwrite((void *)tempstr, sizeof(userobjstr), 1, fileptr);

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

/* Return sucess or failure.                            */
  return (numwritten == 1);
}

/*------------------------------------------------------*/
/*  Function to calculate the RA and Dec from the data  */
/*                 pointed to by ptr.                   */
/*------------------------------------------------------*/
static void userobj_radec(userobjstr *ptr, REAL *raptr, REAL *decptr)
{
  REAL hours, degrees;

  hours   = (REAL)(ptr->rah)  + (REAL)(ptr->ram)  / (REAL)60.0;
  degrees = (REAL)(ptr->decd) + (REAL)(ptr->decm) / (REAL)60.0;

  *raptr = CONV * (REAL)15.0 * hours;
  if (*raptr >= TWO_PI) *raptr = ZERO;

  *decptr = CONV * degrees;
  if (!ptr->isn) *decptr = -(*decptr);
  if (*decptr >  PIby2) *decptr =  PIby2;
  if (*decptr < -PIby2) *decptr = -PIby2;

  return;
}

/********************************************************/
/*                 List-Building Function               */
/********************************************************/
static void userobj_buildfn(void)
{
  plotobj userobj;

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

/* Choose to keep plotting list up-to-date with all     */
/* changes in Observer details, so omit check on        */
/* whether module is enabled or not.                    */

/* Any pending rebuild request is now being met.        */
  request_pending = FALSE;

/*Build plotobj for the one object owned by this module.*/
    userobj.id = 0;
    userobj.module = moduleid;
    userobj.plotfn  = userobj_plotfn;
    radec_altaz(ra, dec, &ob_data, ob_data.sid, &userobj.alt, &userobj.azim);
    userobj.size = size;

    /* Offer this to the main program.                  */
    addobj(userobj, &horiz_id, &vert_id);

  return;
}


/********************************************************/
/*              Object-Selecting Function               */
/********************************************************/
static void userobj_selectfn(selectfn_reasoncode reason)
{

  switch (reason)
  {
    case new_selection:
      /* Only one User Object, so selection always      */
      /* succeeds.                                      */
      selection.id = 0;
      selection.selected_OK = TRUE;
    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.                       */
      /*                                                */
      /* Fill in 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.    */
      userobj_infofn(0);
      break;

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

    }

  return;
}

/*------------------------------------------------------*/
/*  Function to fill in selection details of selected   */
/* User Object (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.    */

/* Record window IDs of User Object.                    */
  selection.horiz_id    = horiz_id;
  selection.vert_id     = vert_id;

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

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

  radec_phenomena(ra, 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(ra, dec, &ob_data, ch,
        &selection.rise_azim,
        &selection.rise_hour,
        &selection.rise_min);

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

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

  return;
}

/********************************************************/
/*                   Display Function                   */
/********************************************************/
static BOOL userobj_displayfn(BOOL *enabptr)
{
  BOOL changes;

/* Respond to click on Display menu entry,  or to move- */
/* ment of pointer over arrow on menu entry.            */

/* Find out whether or not event was a menu choice.     */
  if (wimpt_last_event()->e == wimp_EMENU)
  {
/* Menu choice.                                         */
  /* Toggle Enable/Disable flag.                        */
    display_flag = !display_flag;

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

  /* Issue a Rebuild Request if one is pending.         */
    rebuild_request = request_pending;

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

/* User has invoked Display dialogue box.               */
/* Create dialogue box, load it with data, and put it   */
/* on display (if not already done).                    */
  if (!dbox_persisting)
  {
    d_disp = dbox_new(DISPBOX_NAME);
    if (d_disp == NULL) return FALSE;
    load_dispbox(&current_data);
    dbox_show(d_disp);
  }

/* Deal with clicks on buttons until action by main     */
/* program is required.  Fn returns TRUE if changes     */
/* affecting the appearance of the windows have been    */
/* made.                                                */
  changes = dispbox_buttons();

/* Delete disp box if it is not required to persist.    */
  if (!dbox_persisting) dbox_dispose(&d_disp);

/* Plotting lists will need rebuilding if significant   */
/* changes have been made.                              */
/* If module is currently enabled, issue request now.   */
/* If not, make a note that a request is pending.       */
  rebuild_request = (changes || request_pending) &&  display_flag;
  request_pending = (changes || request_pending) && !display_flag;

/* Windows will need updating if and only if plotting   */
/* lists need rebuilding.                               */
  return rebuild_request;
}

/*------------------------------------------------------*/
/*  Function to load dbox with data pointed to by ptr.  */
/*------------------------------------------------------*/
static void load_dispbox(userobjstr *ptr)
{
  char numbuf[16];

/* Right Ascension (hour part, then minute part):       */
  sprintf(numbuf, "%02.2i", ptr->rah);
  dbox_setfield(d_disp, rah_field, numbuf);
  sprintf(numbuf, "%02.2i", ptr->ram);
  dbox_setfield(d_disp, ram_field, numbuf);

/* Declination (degrees, then minutes, then North/South)*/
  sprintf(numbuf, "%02.2i", ptr->decd);
  dbox_setfield(d_disp, decd_field, numbuf);
  sprintf(numbuf, "%02.2i", ptr->decm);
  dbox_setfield(d_disp, decm_field, numbuf);
  dbox_setfield(d_disp, ns_field, (ptr->isn ? "N" : "S"));

/* Three lines of text:                                 */
  dbox_setfield(d_disp, text1_field, ptr->text1);
  dbox_setfield(d_disp, text2_field, ptr->text2);
  dbox_setfield(d_disp, text3_field, ptr->text3);

  return;
}

/*------------------------------------------------------*/
/*  Function to read the data in the Display dbox, and  */
/*    copy them to the userobjstr pointed to by ptr.    */
/*------------------------------------------------------*/
static void read_dispbox(userobjstr *ptr)
{
  char nsbuf[2];

/* Right Ascension (hour part, then minute part):       */
  ptr->rah = dbox_getnumeric(d_disp, rah_field);
  ptr->ram = dbox_getnumeric(d_disp, ram_field);

/* Declination (degrees, minutes, North / South):       */
  ptr->decd = dbox_getnumeric(d_disp, decd_field);
  ptr->decm = dbox_getnumeric(d_disp, decm_field);
  dbox_getfield(d_disp, ns_field, nsbuf, 2);
  ptr->isn = (nsbuf[0] == 'N');

/* Three lines of text:                                 */
  dbox_getfield(d_disp, text1_field, ptr->text1, USEROBJ_TEXTLEN);
  dbox_getfield(d_disp, text2_field, ptr->text2, USEROBJ_TEXTLEN);
  dbox_getfield(d_disp, text3_field, ptr->text3, USEROBJ_TEXTLEN);

  return;
}

/*------------------------------------------------------*/
/* Function to handle clicks on buttons in Display dbox.*/
/* Returns TRUE if changes have been made which require */
/*           the re-drawing of the windows.             */
/*------------------------------------------------------*/
static BOOL dispbox_buttons(void)
{
  int response;          /* Button No. clicked by user. */
  BOOL changes;          /* TRUE if significant changes.*/
  userobjstr temp_data;  /* Temp store for dbox data.   */

/* Loop until user clicks on OK, or on Cancel with left */
/* hand mouse button, or destroys dbox by (eg) clicking */
/* outside it.                                          */

  while (TRUE)
  {

  /* Get button number of latest click.                 */
    response = dbox_fillin(d_disp);

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

      case box_destroy:
      /* Box destroyed.  Close it & return.             */
        dbox_persisting = FALSE;
        return FALSE;

      case ok_butt:
      /* Read data in dbox.                             */
        read_dispbox(&temp_data);
      /* Copy to current_data, noting whether there have*/
      /* been any changes requiring the re-drawing of   */
      /* the windows.                                   */
        changes = copy_data(&temp_data, &current_data);
        dbox_persisting = dbox_persist();
      /* Can safely ignore clicks with Adjust when no   */
      /* significant changes have been made.            */
        if (dbox_persisting && !changes) break;
      /* Calc new RA and Dec, if necessary.             */
        if (changes) userobj_radec(&current_data, &ra, &dec);
        return changes;

      case cancel_butt:
      /* If click was with Select, close dbox & return. */
        if (!dbox_persist())
        { dbox_persisting = FALSE;
          return FALSE; }
      /* If Adjust, reload current data and continue    */
      /* looping.                                       */
        load_dispbox(&current_data);
        break;

      case show_butt:
      /* Load default settings from disc.   If error,   */
      /* report it and continue; else display new values*/
        if (!read_discfile(&temp_data))
          werr(NON_FATAL, "Can't load defaults");
        else
          load_dispbox(&temp_data);
        break;

      case set_butt:
      /* Save dbox settings to disc.  If error, report  */
      /* it & continue looping.                         */
        read_dispbox(&temp_data);
        if (!write_discfile(&temp_data))
        { werr(NON_FATAL, "Can't set defaults");
          break; }
      /* If click was with Select, close dbox and       */
      /* return (no need to re-draw windows). If Adjust,*/
      /* continue looping.                              */
        if (!dbox_persist())
        { dbox_persisting = FALSE;
          return FALSE; }
        break;

      default:
      /* One of the data-changing buttons.              */
        dispbox_databutts(response);
        break;

    }
  }
}

/*------------------------------------------------------*/
/*    Function to handle clicks on the data-changing    */
/*         buttons in the Display dialogue box.         */
/*------------------------------------------------------*/
static void dispbox_databutts(int response)
{
  char numbuf[16];
  char ns_buf[2];
  int hours, minutes, degrees;

  switch (response) {

    case rah_tu: case rah_td: case rah_uu: case rah_ud:
    /* Change Hour part of Right Ascension.             */
      hours = dbox_getnumeric(d_disp, rah_field);
      hours += addon[response - rah_tu];
      if (hours >= 0  &&  hours < 24)
      {
        sprintf(numbuf, "%02.2i", hours);
        dbox_setfield(d_disp, rah_field, numbuf);
      }
      break;

    case ram_tu: case ram_td: case ram_uu: case ram_ud:
    /* Change Minute part of Right Ascension.           */
      minutes = dbox_getnumeric(d_disp, ram_field);
      minutes += addon[response - ram_tu];
      if (minutes >= 0  &&  minutes < 60)
      {
        sprintf(numbuf, "%02.2i", minutes);
        dbox_setfield(d_disp, ram_field, numbuf);
      }
      break;

    case decd_tu: case decd_td: case decd_uu: case decd_ud:
    /* Change Degree part of Declination.               */
      degrees  = dbox_getnumeric(d_disp, decd_field);
      degrees += addon[response - decd_tu];
      minutes  = dbox_getnumeric(d_disp, decm_field);
      if ((degrees >=  0  &&  degrees <  90) ||
          (degrees == 90  &&  minutes ==  0))
      {
        sprintf(numbuf, "%02.2i", degrees);
        dbox_setfield(d_disp, decd_field, numbuf);
      }
      break;

    case decm_tu: case decm_td: case decm_uu: case decm_ud:
    /* Change Minute part of Declination.               */
      minutes  = dbox_getnumeric(d_disp, decm_field);
      minutes += addon[response - decm_tu];
      if (minutes >=  0  &&  minutes <  60)
      {
        sprintf(numbuf, "%02.2i", minutes);
        dbox_setfield(d_disp, decm_field, numbuf);
      }
      break;

    case ns_butt: case ns_field:
    /* Toggle North / South setting.                    */
      dbox_getfield(d_disp, ns_field, ns_buf, 2);
      ns_buf[0] = (ns_buf[0] == 'N' ?  'S' : 'N' );
      dbox_setfield(d_disp, ns_field, ns_buf);
      break;

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

  }
  return;
}

/*------------------------------------------------------*/
/*   Function to check whether two userobjstrs differ   */
/*  significantly (ie in a way which would require the  */
/*  windows to be re-drawn) and then copy the contents  */
/*                of one into the other.                */
/*------------------------------------------------------*/
static BOOL copy_data(userobjstr *ptr1, userobjstr *ptr2)
{
  BOOL changes;

  changes = ptr1->rah  != ptr2->rah   ||
            ptr1->ram  != ptr2->ram   ||
            ptr1->decd != ptr2->decd  ||
            ptr1->decm != ptr2->decm  ||
           ( ptr1->isn && !ptr2->isn) ||
           (!ptr1->isn &&  ptr2->isn);

  *ptr2 = *ptr1;

  return changes;
}


/********************************************************/
/*                   Plotting Function                  */
/********************************************************/
static os_error *userobj_plotfn(int x, int y, int id)
{
  return sv_plotsprite(&spr_id, MODE_20, x, y, 5, 5);
}


/********************************************************/
/*                     Info Function                    */
/********************************************************/
static void userobj_infofn(int id)
{
  sprintf(infoptr,
          "User Object\nRA %02ih %02im   Dec %02i %02i' %c\n%s\n%s\n%s",
          current_data.rah,
          current_data.ram,
          current_data.decd,
          current_data.decm,
         (current_data.isn ? 'N' : 'S'),
          current_data.text1,
          current_data.text2,
          current_data.text3);

  return;
}
