/*
** Astrolog (Version 3.05) File: general.c
**
** IMPORTANT: The planetary calculation routines used in this program
** have been Copyrighted and the core of this program is basically a
** conversion to C of the routines created by James Neely as listed in
** Michael Erlewine's 'Manual of Computer Programming for Astrologers',
** available from Matrix Software. The copyright gives us permission to
** use the routines for our own purposes but not to sell them or profit
** from them in any way.
**
** IN ADDITION: the graphics database and chart display routines used in
** this program are Copyright (C) 1991-1993 by Walter D. Pullen. Permission
** is granted to freely use and distribute these routines provided one
** doesn't sell, restrict, or profit from them in any way. Modification
** is allowed provided these notices remain with any altered or edited
** versions of the program.
*/

#include "astrolog.h"


/*
*******************************************************************************
** General and numeric routines
*******************************************************************************
*/

/* Swap two real float values. */

void SwapReal(d1, d2)
real *d1, *d2;
{
  real temp;

  temp = *d1; *d1 = *d2; *d2 = temp;
}


/* Return the length of a string. */

int StringLen(line)
char *line;
{
  int i;

  for (i = 0; *line++; i++)
    ;
  return i;
}


/* Determine the sign of a number: -1 if value negative, +1 if value */
/* positive, and 0 if it's zero.                                     */

real Sgn(d)
real d;
{
  return d < 0.0 ? -1.0 : (d > 0.0 ? 1.0 : 0.0);
}


/* Convert an inputed fractional degrees/minutes value to a true decimal   */
/* degree quantity. For example, the user enters the decimal value "10.30" */
/* to mean 10 degrees and 30 minutes; this will return 10.5, i.e. 10       */
/* degrees and 30 minutes expressed as a floating point degree value.      */

real DecToDeg(d)
real d;
{
  return Sgn(d)*(floor(dabs(d))+FRACT(dabs(d))*100.0/60.0);
}


/* Modulus function for floating point values. The modulus value itself */
/* has been specified earlier: it is usually either 360.0 or PI/2.0.    */

real Mod(d)
real d;
{
  if (d > modulus)         /* In most cases, our value is only slightly */
    d -= modulus;          /* out of range, so we can test for it and   */
  else if (d < modulus)    /* avoid the more complicated arithmetic.    */
    d += modulus;
  if (d >= 0 && d < modulus)
    return d;
  return (d - floor(d/modulus)*modulus);
}


/* A similar modulus function: convert an integer to value from 1..12. */

int Mod12(i)
int i;
{
  while (i > SIGNS)
    i -= SIGNS;
  while (i < 1)
    i += SIGNS;
  return i;
}


/* Return the shortest distance between two degrees in the zodiac. This is  */
/* normally their difference, but we have to check if near the Aries point. */

real MinDistance(deg1, deg2)
real deg1, deg2;
{
  real i;

  i = dabs(deg1-deg2);
  return i < 180 ? i : DEGREES - i;
}


/* Return the degree of the midpoint between two zodiac positions, making */
/* sure we return the true midpoint closest to the positions in question. */

real Midpoint(deg1, deg2)
real deg1, deg2;
{
  real mid;

  mid = (deg1+deg2)/2.0;
  return MinDistance(deg1, mid) < 90.0 ? mid : Mod(mid+180.0);
}


/* Given a planet and sign, determine whether: The planet rules the sign, */
/* the planet has its fall in the sign, the planet exalts in the sign, or */
/* is debilitated in the sign; and return an appropriate character.       */

char Dignify(body, sign)
int body, sign;
{
  if (body > OBJECTS)
    return ' ';
  if (ruler1[body] == sign || ruler2[body] == sign)
    return 'R';
  if (ruler1[body] == Mod12(sign+6) || ruler2[body] == Mod12(sign+6))
    return 'F';
  if (exalt[body] == sign)
    return 'e';
  if (exalt[body] == Mod12(sign+6))
    return 'd';
  return '-';
}


/* Determine the number of days in a particular month. The year is needed, */
/* too, because we have to check for leap years in the case of February.   */

int DayInMonth(month, year)
int month, year;
{
  return (month == 9 || month == 4 || month == 6 || month == 11 ? 30 :
    (month != 2 ? 31 : 28 +
    (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0))));
}


/* Given an aspect and two objects making that aspect with each other,   */
/* return the maximum orb allowed for such an aspect. Normally this only */
/* depends on the aspect itself, but some objects require narrow orbs,   */
/* and some allow wider orbs, so check for these cases.                  */

real Orb(body1, body2, aspect)
int body1, body2, aspect;
{
  real orb, i;

  orb = aspectorb[aspect];
  i = body1 > BASE ? 2.0 : (body1 > OBJECTS ? 360.0 : planetorb[body1]);
  orb = MIN(orb, i);
  i = body2 > BASE ? 2.0 : (body2 > OBJECTS ? 360.0 : planetorb[body2]);
  orb = MIN(orb, i);
  if (body1 <= OBJECTS)
    orb += planetadd[body1];
  if (body2 <= OBJECTS)
    orb += planetadd[body2];
  return orb;
}


/*
*******************************************************************************
** IO routines
*******************************************************************************
*/

/* Exit the program, and do any cleanup necessary. */

void Terminate(value)
int value;
{
  if (ansi)
    printf("%c[0m", ESCAPE);    /* Get out of any Ansi color mode. */
  exit(value);
}


/* Simplification for a commonly printed error message. */

void TooFew(option)
char *option;
{
  fprintf(stderr, "%s: Too few options to switch -%s\n", appname, option);
  Terminate(1);
}


/* More simplifications for commonly printed error messages. */

void BadVal(option, value)
char *option;
int value;
{
  fprintf(stderr, "%s: Value %d passed to switch -%s out of range.\n",
    appname, value, option);
  Terminate(1);
}

void BadVal2(option, value)
char *option;
real value;
{
  fprintf(stderr, "%s: Value %.0f passed to switch -%s out of range.\n",
    appname, value, option);
  Terminate(1);
}


/* A simple procedure used throughout Astrolog: Print a particular */
/* character on the screen 'n' times.                              */

void PrintTab(chr, count)
char chr;
int count;
{
  int i;

  for (i = 0; i < count; i++)
    putchar(chr);
}


/* Set an Ansi text color. */

void AnsiColor(col)
int col;
{
  if (!ansi)
    return;
  printf("%c[", ESCAPE);
  if (col < 0)              /* Hack: Negative color means normal text. */
    putchar('0');
  else
    printf("%c;%d", '0' + (col > 7), 30 + (col & 7));
  putchar('m');
}


/* Nicely format the current longitude and latitude locations and return  */
/* them in a string. Various parts of the program display a chart header, */
/* and this allows the similar computations to be coded only once.        */

char *StringLocation(Lon, Lat, norm)
real Lon, Lat, norm;
{
  static char loc[14];
  int i, j;

  i = (int) (FRACT(dabs(Lon))*norm+0.1);
  j = (int) (FRACT(dabs(Lat))*norm+0.1);
  sprintf(loc, "%3.0f%c%d%d%c %2.0f%c%d%d%c",
    floor(dabs(Lon)), DEGR1, i/10, i%10, Lon < 0.0 ? 'E' : 'W',
    floor(dabs(Lat)), DEGR1, j/10, j%10, Lat < 0.0 ? 'S' : 'N');
  return loc;
}


/* Return the next value in a stream of values describing how to calculate */
/* the positions of the various planets.                                   */

real ReadPlanetData(reset)
int reset;
{
  static real *datapointer = planetdata;

  if (!reset)
    return *datapointer++;
  datapointer = planetdata;
  return 0.0;
}


/* Similarly, return the next value in a stream of values describing the */
/* locations of the various fixed stars.                                 */

real ReadStarData(reset)
int reset;
{
  static real *datapointer = stardata;

  if (!reset)
    return *datapointer++;
  datapointer = stardata;
  return 0.0;
}


#ifdef GRAPH
/* Another stream reader, this one is used by the globe drawing routine: */
/* for the next body of land/water, return its name (and color), its     */
/* longitude and latitude, and a vector description of its outline.      */

int ReadWorldData(nam, loc, lin)
char **nam, **loc, **lin;
{
  static char **datapointer = worlddata;

  *loc = *datapointer++;
  *lin = *datapointer++;
  *nam = *datapointer++;
  if ((exdisplay & DASHXP0) && xbitmap)
    printf("%s\n", *nam+1);
  if (*loc[0])
    return TRUE;
  datapointer = worlddata;    /* Reset stream when no data left. */
  return FALSE;
}
#endif


/* Print a zodiac position on the screen. */

void PrintMinute(deg)
real deg;
{
  int sign, d, m;

  if (!(operation & DASHs0)) {

    /* Normally, we print out the position in degrees/sign/minutes format: */

    deg = Mod(deg + 1.0/60.0/2.0);
    AnsiColor(elemansi[(int) (deg / 30.0) & 3]);
    sign = (int) (deg / 30.0);
    d = (int) deg - sign*30;
    m = (int) (FRACT(deg)*60.0);
    printf("%2d%c%c%c%s%d", d, SIGNAM(sign + 1), m < 10 ? "0" : "", m);
  } else {

    /* However, if -s0 switch in effect, print position in hours/minutes: */

    deg = Mod(deg + 1.0/4.0/2.0);
    AnsiColor(elemansi[(int) (deg / 30.0) & 3]);
    d = (int) (deg / 15.0);
    m = (int) ((deg - (real)d*15.0)*60.0/24.0);
    printf("%2dh,%s%dm", d, m < 10 ? "0" : "", m);
  }
  AnsiColor(-1);
}


/* This is similar to printing out a zodiac degree, but here we print out */
/* a (signed) declination value in degrees and minutes.                   */

void PrintAltitude(deg)
real deg;
{
  int d, m;

  while (deg > 90.0)    /* Make sure declination value is from -90..+90 deg. */
    deg -= 180.0;
  while (deg < -90.0)
    deg += 180.0;
  putchar(deg < 0.0 ? '-' : '+');
  deg = dabs(deg) + 1.0/60.0/2.0;
  d = (int) deg;
  m = (int) (FRACT(deg)*60.0);
  printf("%2d%c%s%d'", d, DEGR2, m < 10 ? "0" : "", m);
}


/* Prompt the user for a floating point value, and make sure it conforms  */
/* to the specified bounds before returning it. If a non-numeric value is */
/* entered, then assume it's the name of a month, and try to convert it   */
/* to the appropriate number from 1 to 12; and also check for an "AM" or  */
/* "PM" suffix to hour values, and adjust the number appropriately.       */

real Input(prompt, low, high)
char *prompt;
real low, high;
{
  char line[STRING], c;
  real x;
  int i, j;

  while (TRUE) {
    printf("%s", prompt);
    AnsiColor(YELLOW);
    printf(" > ");
    AnsiColor(-1);
    if (gets(line) == (char *) NULL) {
      printf("\n%s terminated.\n", appname);
      Terminate(2);
    }
    i = StringLen(line);
    for (j = 0; j < i; j++)
      if (line[j] == ':')      /* Convert all colons in the */
        line[j] = '.';         /* entered line to periods.  */
    c = CAP(line[0]);

    /* If they entered a string, then check to see if it's a month name. */

    if (c >= 'A' && c <= 'Z') {
      switch (c) {
      case 'J': x = CAP(line[1]) == 'U' ?                   /* January,     */
        (CAP(line[2]) == 'L' ? 7.0 : 6.0) : 1.0; break;     /* June,July    */
      case 'F': x = 2.0; break;                             /* February     */
      case 'M': x = CAP(line[2]) == 'Y' ? 5.0 : 3.0; break; /* March,May    */
      case 'A': x = CAP(line[1]) == 'U' ? 8.0 : 4.0; break; /* April,August */
      case 'S': x = 9.0; break;                             /* September    */
      case 'O': x = 10.0; break;                            /* October      */
      case 'N': x = 11.0; break;                            /* November     */
      case 'D': x = 12.0; break;                            /* December     */
      default: x = 0.0;
      }
    } else {
      sscanf(line, "%lf", &x);    /* Convert entered line to number. */
      i = StringLen(line)-1;
      if (i > 0 && CAP(line[i]) == 'M')
        i--;
      if (i > 0) {
        c = CAP(line[i]);
        if (c == 'A')                    /* Adjust value appropriately */
          x = x >= 12.0 ? x-12.0 : x;    /* if 'AM' or 'PM' suffix.    */
        else if (c == 'P')
          x = x >= 12.0 ? x : x+12.0;
      }
    }
    if (x >= low && x <= high)
      return x;
    printf("Value out of range of from %.0f to %.0f.\n", low, high);
  }
}


/* This important procedure gets all the parameters defining the chart that  */
/* will be worked with later. Given a "filename", it gets from it all the    */
/* pertinent chart information. This is more than just reading from a file - */
/* the procedure also takes care of the cases of prompting the user for the  */
/* information and using the time functions to determine the date now - the  */
/* program considers these cases "virtual" files. Furthermore, when reading  */
/* from a real file, we have to check if it was written in the -o0 format.   */

void InputData(filename)
char *filename;
{
  FILE *data;
  char name[STRING], c;
  int i;
  real k, l, m;
#ifdef TIME
  struct tm curtime;
  long curtimer;

  /* If we are to read from the file "now" then that means use the time */
  /* functions to calculate the present date and time.                  */

  if (filename[0] == 'n' && filename[1] == 'o' && filename[2] == 'w' &&
      filename[3] == 0) {
    autom = 1;
    curtimer = (long) time((long *) 0);
    curtime = *localtime(&curtimer);
    M = (real) curtime.tm_mon + 1.0;
    D = (real) curtime.tm_mday;
    Y = (real) curtime.tm_year + 1900.0;
    F = (real) curtime.tm_hour + (real) curtime.tm_min / 100.0 +
      (real) curtime.tm_sec / 6000.0;
    X = defzone; L5 = deflong; LA = deflat;
    return;
  }
#endif

  /* If we are to read from the file "tty" then that means prompt the user */
  /* for all the chart information.                                        */

  if (filename[0] == 't' && filename[1] == 't' && filename[2] == 'y' &&
      filename[3] == 0) {
    AnsiColor(WHITE);
    printf("** %s version %s (%s) **\n", appname, VERSION, ADDRESS);
    AnsiColor(-1);
#ifdef SWITCHES
    printf("   Invoke as 'astrolog %cH' for brief list of command options.\n",
      DASH);
#endif
    M = Input("Enter month of birth (e.g. '7', 'Jul')", 1.0, 12.0);
    D = Input("Enter day   of birth (e.g. '1', '31') ", 1.0,
      (real) DayInMonth((int) M, 0));
    Y = Input("Enter year  of birth (e.g. '1993')    ", -5000.0, 5000.0);
    printf("Subtract one hour if Daylight Saving time in effect.\n");
    F = Input("Enter time  of birth (e.g. '18:30' '6:30pm')", -2.0, 24.0);
    printf("Negative values imply time zones east of Greenwich.\n");
    X = Input("Time zone in hours before GMT (5=Eastern, 8=Pacific)",
      -24.0, 24.0);
    printf("Negative values imply eastern and southern locations.\n");
    L5 = Input("Longitude west of place (i.e. DEG:MIN)", -180.0, 180.0);
    LA = Input("Latitude north of place (i.e. DEG:MIN)", -90.0, 90.0);
    putchar('\n');
    return;
  }

  /* Now that the special cases are taken care of, we can assume we are */
  /* to read from a real file.                                          */

  autom = 1;
  data = fopen(filename, "r");      /* Look for file in current directory. */
  if (data == NULL) {
    sprintf(name, "%s%s", DEFAULT_DIR, filename);    /* Look for file in   */
    data = fopen(name, "r");                         /* default directory. */
    if (data == NULL) {
      fprintf(stderr, "%s: File '%s' not found.\n", appname, filename);
      Terminate(1);
    }
  }

  /* Read the chart parameters from a normal file. */

  if ((c = getc(data)) != 'S') {
    ungetc(c, data);
    fscanf(data, "%lf%lf%lf%lf%lf%lf%lf", &M, &D, &Y, &F, &X, &L5, &LA);

  /* Read the actual chart positions from a file produced with the -o0. */

  } else {

    /* Hack: A negative month value means the chart parameters are invalid, */
    /* hence -o0 is in effect and we can assume the chart positions are     */
    /* already in memory so we don't have to calculate them later.          */

    M = -1.0;
    for (i = 1; i <= OBJECTS; i++) {
      fscanf(data, "%s%lf%lf%lf", name, &k, &l, &m);
      planet[i] = (l-1.0)*30.0+k+m/60.0;
      fscanf(data, "%s%lf%lf", name, &k, &l);
      planetalt[i] = k+l/60.0;
      ret[i] = DTOR(name[1] == 'D' ? 1.0 : -1.0);
    }
    for (i = 1; i <= SIGNS/2; i++) {
      fscanf(data, "%s%lf%lf%lf", name, &k, &l, &m);
      house[i+6] = Mod((house[i] = Mod((l-1.0)*30.0+k+m/60.0))+180.0);
    }
  }
  fclose(data);
}

/* general.c */
