/*
** Astrolog (Version 4.00) File: charts.c
**
** IMPORTANT NOTICE: the graphics database and chart display routines
** used in this program are Copyright (C) 1991-1993 by Walter D. Pullen
** (cruiser1@stein.u.washington.edu). 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.
**
** The main 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 personal use but not to sell them or profit from
** them in any way.
**
** The PostScript code within the core graphics routines are programmed
** and Copyright (C) 1992-1993 by Brian D. Willoughby
** (brianw@sounds.wa.com). Conditions are identical to those above.
**
** The extended accurate ephemeris databases and formulas are from the
** calculation routines in the program "Placalc" and are programmed and
** Copyright (C) 1989,1991,1993 by Astrodienst AG and Alois Treindl
** (alois@azur.ch). The use of that source code is subject to
** regulations made by Astrodienst Zurich, and the code is not in the
** public domain. This copyright notice must not be changed or removed
** by any user of this program.
**
** Initial programming 8/28,30, 9/10,13,16,20,23, 10/3,6,7, 11/7,10,21/1991.
** X Window graphics initially programmed 10/23-29/1991.
** PostScript graphics initially programmed 11/29-30/1992.
** Last code change made 12/31/1993.
*/

#include "astrolog.h"


/*
******************************************************************************
** Single Chart Display Subprograms.
******************************************************************************
*/

/* Fill out tables based on the number of unrestricted planets in signs by  */
/* element, signs by mode, as well as other values such as the number of    */
/* objects in yang vs. yin signs, in various house hemispheres (north/south */
/* and east/west), and the number in first six signs vs. second six signs.  */
/* This is used by the -v chart listing and the sidebar in graphics charts. */

void CreateElemTable(elemode, elem, mo, tot, pos, abo, lef, lea)
int elemode[4][3], *elem, *mo, *tot, *pos, *abo, *lef, *lea;
{
  int i, j;

  *tot = *pos = *abo = *lef = *lea = 0;    /* Initialize arrays     */
  for (i = 0; i < 4; i++)                  /* and variables to zero */
    elem[i] = 0;
  for (j = 0; j < 3; j++)
    mo[j] = 0;
  for (i = 0; i < 4; i++)
    for (j = 0; j < 3; j++)
      elemode[i][j] = 0;

  /* Calculate number of objects in each element, mode, hemisphere, etc. */

  for (i = 1; i <= total; i++) if (!ignore[i]) {
    (*tot)++;
    j = ZTOS(planet[i]);
    elemode[(j-1)&3][(j-1)%3]++;
    elem[(j-1)&3]++; mo[(j-1)%3]++;
    *pos += (j & 1);
    *lea += (j < _LIB);
    j = inhouse[i];
    *abo += (j >= _LIB);
    *lef += (j < _CAN || j >= _CAP);
  }
}


/* Print the straight listing of planet and house positions and specified */
/* by the -v switch, along with the element table, etc.                   */

void ChartLocation()
{
  int elemode[4][3], elem[4], mo[3], pos, abo, lef, lea;
  int count, i, j, k;

  CreateElemTable(elemode, elem, mo, &count, &pos, &abo, &lef, &lea);

  /* Print header showing time and date of the chart being displayed. */

  AnsiColor(WHITE);
  fprintf(S, "%s %s chart ", appname, VERSION);
  if (Mon == -1)
    fprintf(S, "(no time or space)\n");
  else if (relation == DASHrc)
    fprintf(S, "(composite)\n");
  else {
    i = (int) (FRACT(dabs(Tim))*100.0+ROUND);
    j = (int) (FRACT(dabs(Zon))*100.0+ROUND);
    fprintf(S, "for %s %s",
      CharDate(Mon, Day, Yea, TRUE), CharTime((int)Tim, i));
    fprintf(S, " (%c%.0f:%02d GMT) ",
      Zon > 0.0 ? '-' : '+', dabs(Zon), j);
    fprintf(S, "%s\n", CharLocation(Lon, Lat, 100.0));
  }

#ifdef INTERPRET
  if (interpret) {            /* Print an interpretation if -I in effect. */
    if (relation == DASHr)
      InterpretSynastry();    /* Print synastry interpretaion for -r -I.  */
    else
      InterpretLocation();    /* Do normal interpretation for just -v -I. */
    return;
  }
#endif
  AnsiColor(DEFAULT);
  fprintf(S, "Body  Locat. Ret. Decl. Rul.      House  Rul. Veloc.    ");
  fprintf(S, "%s Houses.\n\n", systemname[housesystem]);

  /* Ok, now print out each location of each object. */

  for (i = 1, j = 1; i <= BASE; i++, j++) {
    if (i > OBJECTS && (i <= C_HI || ignore[i]))
      continue;
    while (i <= OBJECTS && j <= OBJECTS && ignore[j])
      j++;
    if (i <= OBJECTS && j > OBJECTS)
      PrintTab(' ', 51);
    else {
      if (i > OBJECTS)
        j = i;
      AnsiColor(objectansi[j]);
      fprintf(S, "%-4.4s: ", objectname[j]);
      PrintZodiac(planet[j]);
      fprintf(S, " %c ", ret[j] >= 0.0 ? ' ' : 'R');
      if (j <= THINGS || j > OBJECTS)
        PrintAltitude(planetalt[i]);
      else
        fprintf(S, "_______");
      fprintf(S, " (%c)", Dignify(j, ZTOS(planet[i])));
      k = inhouse[j];
      AnsiColor(signansi(k));
      fprintf(S, " [%2d%c%c house]", k, post[k][0], post[k][1]);
      AnsiColor(DEFAULT);
      fprintf(S, " [%c] ", Dignify(j, k));
      if ((j != _MOO || placalc) && (IsObject(j) || (j == _NOD && placalc)))
        fprintf(S, RTOD(dabs(ret[j])) < 10.0 ? "%c%5.3f" : "%c%5.2f",
          ret[i] < 0.0 ? '-' : '+', RTOD(dabs(ret[j])));
      else
        fprintf(S, "______");
    }

    /* For some lines, we have to append the house cusp positions. */

    if (i <= SIGNS) {
      fprintf(S, "  -  ");
      AnsiColor(signansi(i));
      fprintf(S, "House cusp %2d: ", i);
      PrintZodiac(house[i]);
    }

    /* For some lines, we have to append the element table information. */

    if (i == SIGNS+2)
      fprintf(S, "     Car Fix Mut TOT");
    else if (i > SIGNS+2 && i < SIGNS+7) {
      k = i-(SIGNS+2)-1;
      AnsiColor(elemansi[k]);
      fprintf(S, "  %c%c%c%3d %3d %3d %3d",
        element[k][0], element[k][1], element[k][2],
        elemode[k][0], elemode[k][1], elemode[k][2], elem[k]);
      AnsiColor(DEFAULT);
    } else if (i == SIGNS+7)
      fprintf(S, "  TOT %2d %3d %3d %3d", mo[0], mo[1], mo[2], count);
    else if (i == OBJECTS)
      PrintTab(' ', 23);
    else if (i >= U_LO)
      fprintf(S, "  Uranian #%d", i-U_LO+1);
    switch (i-SIGNS-1) {
    case 1: fprintf(S, "   +:%2d", pos);       break;
    case 2: fprintf(S, "   -:%2d", count-pos); break;
    case 3: fprintf(S, "   M:%2d", abo);       break;
    case 4: fprintf(S, "   N:%2d", count-abo); break;
    case 5: fprintf(S, "   A:%2d", lef);       break;
    case 6: fprintf(S, "   D:%2d", count-lef); break;
    case 7: fprintf(S,    "<:%2d", lea);       break;
    }
    printl();
  }

  /* Do another loop to print out the stars in their specified order. */

  if (universe) for (i = S_LO; i <= S_HI; i++) if (!ignore[i]) {
    j = BASE+starname[i-BASE];
    AnsiColor(objectansi[j]);
    fprintf(S, "%.4s: ", objectname[j]);
    PrintZodiac(planet[j]);
    fprintf(S, "   ");
    PrintAltitude(planetalt[j]);
    k = inhouse[j];
    AnsiColor(signansi(k));
    fprintf(S, "     [%2d%c%c house]", k, post[k][0], post[k][1]);
    AnsiColor(DEFAULT);
    fprintf(S, "     ______  Star #%2d: %5.2f\n", i-BASE, starbright[j-BASE]);
  }
}


/* Print out the aspect and midpoint grid for a chart, as specified with the */
/* -g switch. (Each grid row takes up 4 lines of text.)                      */

void ChartGrid()
{
  int x, y, r, x1, y1, temp;

#ifdef INTERPRET
  if (interpret) {    /* Print interpretation instead if -I in effect. */
    InterpretGrid();
    return;
  }
#endif

  for (y1 = 0, y = 1; y <= total; y++) if (!ignore[y])
    for (r = 1; r <= 4; r++) {
      for (x1 = 0, x = 1; x <= total; x++) if (!ignore[x]) {
        if (y1 > 0 && x1 > 0 && y+r > 2)
          printc(r > 1 ? BOXV : BOXC);
        if (r > 1) {
          temp = grid->n[x][y];

          /* Print aspect rows. */

          if (x < y) {
            if (temp);
              AnsiColor(aspectansi[temp]);
            if (r == 2)
              fprintf(S, "%s", temp ? aspectabbrev[temp] : "   ");
            else if (!temp)
              fprintf(S, "   ");
            else {
              if (r == 3) {
                if (grid->v[x][y] < 600)
                  fprintf(S, "%c%2d", exdisplay & DASHga ?
                    (grid->v[x][y] < 0 ? 'a' : 's') :
                    (grid->v[x][y] < 0 ? '-' : '+'), abs(grid->v[x][y])/60);
                else
                  fprintf(S, "%3d", abs(grid->v[x][y])/60);
              } else
                fprintf(S, "%02d'", abs(grid->v[x][y])%60);
            }

          /* Print midpoint rows. */

          } else if (x > y) {
            AnsiColor(signansi(temp));
            if (r == 2) {
              temp = grid->n[x][y];
              fprintf(S, "%c%c%c", SIGNAM(temp));
            } else if (r == 3) {
              fprintf(S, "%2d%c", grid->v[x][y]/60, DEGR2);
            } else
              fprintf(S, "%02d'", grid->v[x][y]%60);

          /* Print the diagonal of object names. */

          } else {
            AnsiColor(REVERSE);
            if (r == 2) {
              AnsiColor(objectansi[y]);
              fprintf(S, "%c%c%c", OBJNAM(y));
            } else {
              temp = ZTOS(planet[y]);
              AnsiColor(signansi(temp));
              if (r == 3)
                fprintf(S, "%2d%c", (int)planet[y] - (temp-1)*30, DEGR2);
              else
                fprintf(S, "%c%c%c", SIGNAM(temp));
            }
          }
          AnsiColor(DEFAULT);
        } else
          if (y1 > 0)
            PrintTab(BOXH, 3);
        x1++;
        if (column80 && x1 >= 20)
          x = total;
      }
      if (y+r > 2)
        printl();
      y1++;
    }
}


/* This is a subprocedure of DisplayGrands(). Here we print out one aspect */
/* configuration found by the parent procedure.                            */

void PrintGrand(nam, i1, i2, i3, i4)
char nam;
int i1, i2, i3, i4;
{
  switch (nam) {
  case '.': AnsiColor(aspectansi[_CON]); fprintf(S, "Stellium   "); break;
  case 't': AnsiColor(aspectansi[_TRI]); fprintf(S, "Grand Trine"); break;
  case 's': AnsiColor(aspectansi[_OPP]); fprintf(S, "T-Square   "); break;
  case 'y': AnsiColor(aspectansi[_INC]); fprintf(S, "Yod        "); break;
  case 'g': AnsiColor(aspectansi[_SQU]); fprintf(S, "Grand Cross"); break;
  case 'c': AnsiColor(aspectansi[_SEX]); fprintf(S, "Cradle     "); break;
  default: ;
  }
  AnsiColor(DEFAULT);
  fprintf(S, " %s ", nam == '.' || nam == 't' || nam == 'g' ? "with" : "from");
  AnsiColor(objectansi[i1]);
  fprintf(S, "%c%c%c: ", OBJNAM(i1));
  PrintZodiac(planet[i1]);
  fprintf(S, " %s ", nam == '.' || nam == 't' ? "and" : "to ");
  AnsiColor(objectansi[i2]);
  fprintf(S, "%c%c%c: ", OBJNAM(i2));
  PrintZodiac(planet[i2]);
  fprintf(S, " %s ", nam == 'g' || nam == 'c' ? "to " : "and");
  AnsiColor(objectansi[i3]);
  fprintf(S, "%c%c%c: ", OBJNAM(i3));
  PrintZodiac(planet[i3]);
  if (nam == 'g' || nam == 'c') {
    fprintf(S, " to ");
    AnsiColor(objectansi[i4]);
    fprintf(S, "%c%c%c: ", OBJNAM(i4));
    PrintZodiac(planet[i4]);
  }
  printl();
}


/* Scan the aspect grid of a chart and print out any major configurations, */
/* as specified with the -g0 switch.                                       */

void DisplayGrands()
{
  int count = 0, i, j, k, l;

  for (i = 1; i <= total; i++) if (!ignore[i])
    for (j = 1; j <= total; j++) if (j != i && !ignore[j])
      for (k = 1; k <= total; k++) if (k != i && k != j && !ignore[k]) {

        /* Is there a Stellium among the current three planets? */

        if (i < j && j < k && grid->n[i][j] == _CON &&
            grid->n[i][k] == _CON && grid->n[j][k] == _CON) {
          count++;
          PrintGrand('.', i, j, k, l);

        /* Is there a Grand Trine? */

        } else if (i < j && j < k && grid->n[i][j] == _TRI &&
            grid->n[i][k] == _TRI && grid->n[j][k] == _TRI) {
          count++;
          PrintGrand('t', i, j, k, l);

        /* Is there a T-Square? */

        } else if (j < k && grid->n[j][k] == _OPP &&
            grid->n[MIN(i, j)][MAX(i, j)] == _SQU &&
            grid->n[MIN(i, k)][MAX(i, k)] == _SQU) {
          count++;
          PrintGrand('s', i, j, k, l);

        /* Is there a Yod? */

        } else if (j < k && grid->n[j][k] == _SEX &&
            grid->n[MIN(i, j)][MAX(i, j)] == _INC &&
            grid->n[MIN(i, k)][MAX(i, k)] == _INC) {
          count++;
          PrintGrand('y', i, j, k, l);
        }
        for (l = 1; l <= total; l++) if (!ignore[l]) {

          /* Is there a Grand Cross among the current four planets? */

          if (i < j && i < k && i < l && j < l && grid->n[i][j] == _SQU &&
              grid->n[MIN(j, k)][MAX(j, k)] == _SQU &&
              grid->n[MIN(k, l)][MAX(k, l)] == _SQU &&
              grid->n[i][l] == _SQU &&
              MinDistance(planet[i], planet[k]) > 150.0 &&
              MinDistance(planet[j], planet[l]) > 150.0) {
            count++;
            PrintGrand('g', i, j, k, l);

          /* Is there a Cradle? */

          } else if (i < l && grid->n[MIN(i, j)][MAX(i, j)] == _SEX &&
              grid->n[MIN(j, k)][MAX(j, k)] == _SEX &&
              grid->n[MIN(k, l)][MAX(k, l)] == _SEX &&
              MinDistance(planet[i], planet[l]) > 150.0) {
            count++;
            PrintGrand('c', i, j, k, l);
          }
        }
      }
  if (!count)
    fprintf(S, "No major configurations in aspect grid.\n");
}


/* This is subprocedure of ChartWheel(). Here we print out the location */
/* of a particular house cusp as well as what house cusp number it is.  */

void PrintHouse(i, left)
int i, left;
{
  if (!left)
    PrintZodiac(house[i]);
  AnsiColor(signansi(i));
  fprintf(S, "<%d>", i);
  if (left)
    PrintZodiac(house[i]);
  else
    AnsiColor(DEFAULT);
}


/* Another subprocedure of ChartWheel(). Here we print out one line in a */
/* particular house cell (which may be blank).                           */

void PrintWheelSlot(obj, wheelcols)
int obj, wheelcols;
{
  if (obj) {
    AnsiColor(objectansi[obj]);
    fprintf(S, " %c%c%c ", OBJNAM(obj));  /* Print planet and its position. */
    PrintZodiac(planet[obj]);
    fprintf(S, "%c ", ret[obj] < 0.0 ? 'r' : ' ');
    PrintTab(' ', WHEELCOLS-14-1);
  } else
    PrintTab(' ', wheelcols-1);       /* This particular line is blank. */
}


/* Display all the objects in a wheel format on the screen, as specified */
/* with the -w switch. The wheel is divided into the 12 houses and the   */
/* planets are placed accordingly.                                       */

void ChartWheel()
{
  byte wheel[SIGNS][WHEELROWS];
  int wheelcols, count = 0, i, j, k, l;

  /* If the seconds (-b0) flag is set, we'll print all planet and house    */
  /* locations to the nearest zodiac second instead of just to the minute. */

  seconds = -seconds;
  wheelcols = WHEELCOLS + (seconds < 0)*4;

  for (i = 0; i < SIGNS; i++)
    for (j = 0; j < wheelrows; j++)    /* Clear out array from the */
      wheel[i][j] = 0;                 /* last time we used it.    */

  /* This section of code places each object in the wheel house array. */

  for (i = 1; i <= total && count < wheelrows*12; i++) {
    if (ignore[i] || !(i < _MC || i == OBJECTS || i > C_HI))
      continue;

    /* Try to put object in its proper house. If no room, */
    /* then overflow over to the succeeding house.        */

    for (j = inhouse[i]-1; j < SIGNS; j = j < SIGNS ? (j+1)%SIGNS : j) {

      /* Now try to find the proper place in the house to put the object. */
      /* This is in sorted order, although a check is made for 0 Aries.   */

      if (wheel[j][wheelrows-1] > 0)
        continue;
      l = house[j+1] > house[Mod12(j+2)];
      for (k = 0; wheel[j][k] > 0 &&
           (planet[i] >= planet[wheel[j][k]] ||
            (l && planet[i] < DEGHALF && planet[wheel[j][k]] > DEGHALF)) &&
           !(l && planet[i] > DEGHALF && planet[wheel[j][k]] < DEGHALF); k++)
        ;

      /* Actually insert object in proper place. */

      if (wheel[j][k] <= 0)
        wheel[j][k] = i;
      else {
        for (l = wheelrows-1; l > k; l--)
          wheel[j][l] = wheel[j][l-1];
        wheel[j][k] = i;
      }
      count++;
      j = SIGNS;
    }
  }

  /* Now, if this is really the -w switch and not -w0, then reverse the */
  /* order of objects in western houses for more intuitive reading.     */

  if (!(exdisplay & DASHw0))
    for (i = 3; i < 9; i++)
      for (j = 0; j < wheelrows/2; j++) {
        k = wheelrows-1-j;
        l = wheel[i][j]; wheel[i][j] = wheel[i][k]; wheel[i][k] = l;
      }

  /* Here we actually print the wheel and the objects in it. */

  printc(BOXNW); PrintTab(BOXH, WHEELCOLS-8); PrintHouse(11, TRUE);
  PrintTab(BOXH, WHEELCOLS-11); PrintHouse(10, TRUE);
  PrintTab(BOXH, WHEELCOLS-10); PrintHouse(9, TRUE);
  PrintTab(BOXH, wheelcols-4); fprintf(S, "%c\n", BOXNE);
  for (i = 0; i < wheelrows; i++) {
    for (j = 10; j >= 7; j--) {
      printc(BOXV); PrintWheelSlot(wheel[j][i], wheelcols);
    }
    fprintf(S, "%c\n", BOXV);
  }
  PrintHouse(12, TRUE); PrintTab(BOXH, WHEELCOLS-11);
  printc(BOXC); PrintTab(BOXH, wheelcols-1); printc(BOXJN);
  PrintTab(BOXH, wheelcols-1); printc(BOXC); PrintTab(BOXH, WHEELCOLS-10);
  PrintHouse(8, FALSE); printl();
  for (i = 0; i < wheelrows; i++) {
    printc(BOXV); PrintWheelSlot(wheel[11][i], wheelcols); printc(BOXV);

    /* For some rows, we have to insert the chart header information. */

    if (i) {
      PrintTab(' ', wheelcols-11-(i == 2 && !eurotime));
      if (i == 1)
        fprintf(S, "%s (%s) chart", appname, VERSION);
      else if (i == 2) {
        j = DayOfWeek(Mon, Day, Yea);
        k = (int) (FRACT(dabs(Tim))*100.0+ROUND);
        fprintf(S, "%c%c%c %s %s", DAYNAM(j), CharDate(Mon, Day, Yea, 2),
          CharTime((int)floor(Tim), k));
      } else if (i == 3) {
        fprintf(S, "%c%02d:", Zon > 0.0 ? '-' : '+', (int)dabs(Zon));
        j = (int) (FRACT(dabs(Zon))*100.0+ROUND);
        fprintf(S, "%02d %s", j, CharLocation(Lon, Lat, 100.0));
      } else
        PrintTab(' ', 21);
      PrintTab(' ', wheelcols-11-(i == 2 && !eurotime));

    } else
      PrintTab(' ', wheelcols*2-1);
    printc(BOXV); PrintWheelSlot(wheel[6][i], wheelcols);
    fprintf(S, "%c\n", BOXV);
  }
  PrintHouse(1, TRUE); PrintTab(BOXH, WHEELCOLS-10);
  printc(BOXJW); PrintTab(' ', wheelcols-11);
  fprintf(S, "%s", systemname[housesystem]);
  PrintTab(' ', 14-StringLen(systemname[housesystem]));
  fprintf(S, "Houses."); PrintTab(' ', wheelcols-11); printc(BOXJE);
  PrintTab(BOXH, WHEELCOLS-10); PrintHouse(7, FALSE); printl();
  for (i = 0; i < wheelrows; i++) {
    printc(BOXV); PrintWheelSlot(wheel[0][i], wheelcols); printc(BOXV);
    if (i == 0) {
      PrintTab(' ', wheelcols-12);
      fprintf(S, "Julian Day = %9.2f", JulianDayFromTime(T));
      PrintTab(' ', wheelcols-12);
    } else
      PrintTab(' ', wheelcols*2-1);
    printc(BOXV); PrintWheelSlot(wheel[5][i], wheelcols);
    fprintf(S, "%c\n", BOXV);
  }
  PrintHouse(2, TRUE); PrintTab(BOXH, WHEELCOLS-10);
  printc(BOXC); PrintTab(BOXH, wheelcols-1); printc(BOXJS);
  PrintTab(BOXH, wheelcols-1); printc(BOXC);
  PrintTab(BOXH, WHEELCOLS-10); PrintHouse(6, FALSE); printl();
  for (i = 0; i < wheelrows; i++) {
    for (j = 1; j <= 4; j++) {
      printc(BOXV); PrintWheelSlot(wheel[j][i], wheelcols);
    }
    fprintf(S, "%c\n", BOXV);
  }
  printc(BOXSW); PrintTab(BOXH, wheelcols-4); PrintHouse(3, FALSE);
  PrintTab(BOXH, WHEELCOLS-10); PrintHouse(4, FALSE);
  PrintTab(BOXH, WHEELCOLS-10); PrintHouse(5, FALSE);
  PrintTab(BOXH, WHEELCOLS-7); fprintf(S, "%c\n", BOXSE);
  seconds = -seconds;
}


/* Display all aspects between objects in the chart, one per line, in       */
/* sorted order based on the total "power" of the aspect, as specified with */
/* the -m0 switch. The same influences used for -I charts are used here.    */

void ChartAspect()
{
  int pcut = 30000, icut, jcut, phi, ihi, jhi, ahi, p, i, j, k, count = 0;
  real ip, jp;

  loop {
    phi = -1;

    /* Search for the next most powerful aspect in the aspect grid. */

    for (i = 2; i <= total; i++) if (!ignore[i])
      for (j = 1; j < i; j++) if (!ignore[j])
        if (k = grid->n[j][i]) {
          ip = i <= OBJECTS ? objectinf[i] : 2.5;
          jp = j <= OBJECTS ? objectinf[j] : 2.5;
          p = (int) (aspectinf[k]*(ip+jp)/2.0*
            (1.0-dabs((real)(grid->v[j][i]))/60.0/aspectorb[k])*1000.0);
          if ((p < pcut || (p == pcut && (i > icut ||
            (i == icut && j > jcut)))) && p > phi) {
            ihi = i; jhi = j; phi = p; ahi = k;
          }
        }
    if (phi < 0)    /* Exit when no less powerful aspect found. */
      break;
    pcut = phi; icut = ihi; jcut = jhi;
    count++;                               /* Display the current aspect.   */
#ifdef INTERPRET
    if (interpret) {                       /* Interpret it if -I in effect. */
      InterpretAspect(jhi, ihi);
      continue;
    }
#endif
    fprintf(S, "%3d: ", count);
    PrintAspect(jhi, ZTOS(planet[jhi]), (int)Sgn(ret[jhi]), ahi,
      ihi, ZTOS(planet[ihi]), (int)Sgn(ret[ihi]), 'a');
    k = grid->v[jhi][ihi];
    AnsiColor(k < 0 ? WHITE : LTGRAY);
    fprintf(S, " - orb: %c%d%c%02d'",
      exdisplay & DASHga ? (k < 0 ? 'a' : 's') : (k < 0 ? '-' : '+'),
      abs(k)/60, DEGR1, abs(k)%60);
    AnsiColor(DKGREEN);
    fprintf(S, " - power:%6.2f\n", (real) phi/1000.0);
    AnsiColor(DEFAULT);
  }
}


/* Display locations of all midpoints between objects in the chart, */
/* one per line, in sorted zodiac order from zero Aries onward, as  */
/* specified with the -m switch.                                    */

void ChartMidpoint()
{
  int mcut = -1, icut, jcut, mlo, ilo, jlo, m, i, j, count = 0;

  loop {
    mlo = 21600;

    /* Search for the next closest midpoint farther down in the zodiac. */ 

    for (i = 1; i < total; i++) if (!ignore[i])
      for (j = i+1; j <= total; j++) if (!ignore[j]) {
        m = (grid->n[j][i]-1)*30*60 + grid->v[j][i];
        if ((m > mcut || (m == mcut && (i > icut ||
          (i == icut && j > jcut)))) && m < mlo) {
          ilo = i; jlo = j; mlo = m;
        }
      }
    if (mlo >= 21600)    /* Exit when no midpoint farther in zodiac found. */
      break;
    mcut = mlo; icut = ilo; jcut = jlo;
    count++;                               /* Display the current midpoint. */
#ifdef INTERPRET
    if (interpret) {                       /* Interpret it if -I in effect. */
      InterpretMidpoint(ilo, jlo);
      continue;
    }
#endif
    fprintf(S, "%4d: ", count);
    PrintZodiac((real) mlo/60.0);
    printc(' ');
    PrintAspect(ilo, ZTOS(planet[ilo]), (int)Sgn(ret[ilo]), 0,
      jlo, ZTOS(planet[jlo]), (int)Sgn(ret[jlo]), 'm');
    AnsiColor(DEFAULT);
    fprintf(S, "-%4d degree span.\n",
      (int) MinDistance(planet[ilo], planet[jlo]));
  }
}


/* Display locations of the objects on the screen with respect to the local */
/* horizon, as specified with the -Z switch.                                */

void ChartHorizon()
{
  real lon, lat, sx, sy, vx, vy,
    lonz[TOTAL+1], latz[TOTAL+1], azi[TOTAL+1], alt[TOTAL+1];
  int i, j, k, tot;

  lon = DTOR(Mod(Lon)); lat = DTOR(Lat);
  tot = universe ? total : BASE;

  /* First find zenith location on Earth of each object. */

  for (i = 1; i <= tot; i++) {
    lonz[i] = DTOR(planet[i]); latz[i] = DTOR(planetalt[i]);
    EclToEqu(&lonz[i], &latz[i]);
  }

  /* Then, convert this to local horizon altitude and azimuth. */

  for (i = 1; i <= tot; i++) if (i != _MC) {
    lonz[i] = DTOR(Mod(RTOD(lonz[_MC]-lonz[i]+lon)));
    lonz[i] = DTOR(Mod(RTOD(lonz[i]-lon+PI/2.0)));
    EquToLocal(&lonz[i], &latz[i], PI/2.0-lat);
    azi[i] = DEGREES-RTOD(lonz[i]); alt[i] = RTOD(latz[i]);
  }

  /* Now, actually print the location of each object. */

  fprintf(S,
    "Body Altitude Azimuth  Azi. Vector   %s Vector    Moon Vector\n\n",
    centerplanet ? " Sun" : " Earth");
  for (k = 1; k <= tot; k++) {
    i = k <= BASE ? k : BASE+starname[k-BASE];
    if (ignore[i] || !IsThing(i))
      continue;
    AnsiColor(objectansi[i]);
    fprintf(S, "%-4.4s: ", objectname[i]);
    PrintAltitude(alt[i]);

    /* Determine directional vector based on azimuth. */

    j = (int) (FRACT(azi[i])*60.0);
    fprintf(S, " %3d%c%02d'", (int) azi[i], DEGR1, j);
    sx = cos(DTOR(azi[i])); sy = sin(DTOR(azi[i]));
    if (dabs(sx) < dabs(sy)) {
      vx = dabs(sx / sy); vy = 1.0;
    } else {
      vy = dabs(sy / sx); vx = 1.0;
    }
    fprintf(S, " (%.2f%c %.2f%c)",
      vy, sy < 0.0 ? 's' : 'n', vx, sx > 0.0 ? 'e' : 'w');

    /* Determine distance vector of current object from Sun and Moon. */

    vx = azi[1]-azi[i]; vy = azi[2]-azi[i];
    fprintf(S, " [%6.1f%6.1f] [%6.1f%6.1f]",
      dabs(vx) < DEGHALF ? vx : Sgn(vx)*(DEGREES-dabs(vx)), alt[1]-alt[i],
      dabs(vy) < DEGHALF ? vy : Sgn(vy)*(DEGREES-dabs(vy)), alt[2]-alt[i]);
    if (i >= U_LO) {
      if (i <= U_HI)
        fprintf(S, "  Uranian #%d", i-U_LO+1);
      else
        fprintf(S, "  Star #%2d", i-S_LO+1);
    }
    printl();
  }
  AnsiColor(DEFAULT);
}


/* Display x,y,z locations of each body (in AU) with respect to the Sun */
/* (or whatever the specified center planet is), as in the -S switch.   */
/* These values were already determined when calculating the planet     */
/* positions themselves, so this procedure is basically just a loop.    */

void ChartSpace()
{
  real x, y, z;
  int i;

  fprintf(S, "Body     Angle    X axis    Y axis    Z axis    Length\n");
  for (i = 0; i <= BASE; i++) {
    if (ignore[i] || i == 2 || !IsObject(i))
      continue;
    AnsiColor(objectansi[i]);
    fprintf(S, "%c%c%c%c: ", OBJNAM(i),
      objectname[i][3] ? objectname[i][3] : ' ');
    x = spacex[i]; y = spacey[i]; z = spacez[i];
    fprintf(S, "[%7.2f] [%7.2f] [%7.3f] [%7.3f] [%7.3f]",
      planet[i], x, y, z, sqrt(x*x+y*y+z*z));
    if (i >= U_LO) {
      if (i <= U_HI)
        fprintf(S, "  Uranian #%d", i-U_LO+1);
      else
        fprintf(S, "  Star #%2d", i-S_LO+1);
    }
    printl();
  }
  AnsiColor(DEFAULT);
}


/* Print the locations of the astro-graph lines on the Earth as specified */
/* with the -L switch. This includes Midheaven and Nadir lines, zenith    */
/* positions, and locations of Ascendant and Descendant lines.            */

void ChartAstroGraph()
{
  crosstruct PTR c;
  real planet1[TOTAL+1], planet2[TOTAL+1], mc[TOTAL+1], ic[TOTAL+1],
    as[TOTAL+1], ds[TOTAL+1], as1[TOTAL+1], ds1[TOTAL+1],
    lo = Lon, longm, w, x, y, z, ad, oa, am, od, dm;
  int occurcount = 0, tot = total, i, j, k, l, m, n;

  if (exdisplay & DASHL0)
    {
    Allocate(c, sizeof(crosstruct), crosstruct PTR);
    if (c == NULL
#ifdef PC
      /* For PC's the array better not cross a segment boundary. */
      || HIWORD(LOWORD(c) + sizeof(crosstruct)) > 0
#endif
      ) {
      PrintError("Not enough memory for crossing table.");
      return;
    }
  }

#ifdef MATRIX
  for (i = 1; i <= total; i++) {
    planet1[i] = DTOR(planet[i]);
    planet2[i] = DTOR(planetalt[i]);      /* Calculate zenith location on */
    EclToEqu(&planet1[i], &planet2[i]);   /* Earth of each object.        */
  }

  /* Print header. */

  fprintf(S, "Object :");
  for (j = 0, i = 1; i <= total; i++)
    if (!ignore[i] && IsThing(i)) {
      AnsiColor(objectansi[i]);
      fprintf(S, " %c%c%c", OBJNAM(i));
      j++;
      if (column80 && j >= 17) {
        tot = i;
        i = total;
      }
    }
  AnsiColor(DEFAULT);
  fprintf(S, "\n------ :");
  for (i = 1; i <= tot; i++)
    if (!ignore[i] && IsThing(i))
      fprintf(S, " ###");

  /* Print the longitude locations of the Midheaven lines. */

  fprintf(S, "\nMidheav: ");
  if (lo < 0.0)
    lo += DEGREES;
  for (i = 1; i <= tot; i++)
    if (!ignore[i] && IsThing(i)) {
    AnsiColor(objectansi[i]);
    x = DTOR(MC)-planet1[i];
    if (x < 0.0)
      x += 2.0*PI;
    if (x > PI)
      x -= 2.0*PI;
    z = lo+RTOD(x);
    if (z > DEGHALF)
      z -= DEGREES;
    mc[i] = z;
    fprintf(S, "%3.0f%c", dabs(z), z < 0.0 ? 'e' : 'w');
  }
  AnsiColor(DEFAULT);

  /* The Nadir lines are just always 180 degrees away from the Midheaven. */

  fprintf(S, "\nNadir  : ");
  for (i = 1; i <= tot; i++)
    if (!ignore[i] && IsThing(i)) {
    AnsiColor(objectansi[i]);
    z = mc[i] + DEGHALF;
    if (z > DEGHALF)
      z -= DEGREES;
    ic[i] = z;
    fprintf(S, "%3.0f%c", dabs(z), z < 0.0 ? 'e' : 'w');
  }
  AnsiColor(DEFAULT);

  /* Print the Zenith latitude locations. */

  fprintf(S, "\nZenith : ");
  for (i = 1; i <= tot; i++)
    if (!ignore[i] && IsThing(i)) {
      AnsiColor(objectansi[i]);
      y = RTOD(planet2[i]);
      fprintf(S, "%3.0f%c", dabs(y), y < 0.0 ? 's' : 'n');
      as[i] = ds[i] = as1[i] = ds1[i] = LARGE;
    }
  printl2();

  /* Now print the locations of Ascendant and Descendant lines. Since these */
  /* are curvy, we loop through the latitudes, and for each object at each  */
  /* latitude, print the longitude location of the line in question.        */

  longm = DTOR(Mod(MC+lo));
  for (j = 80; j >= -80; j -= graphstep) {
    AnsiColor(DEFAULT); 
    fprintf(S, "Asc@%2d%c: ", j >= 0 ? j : -j, j < 0 ? 's' : 'n');
    for (i = 1; i <= tot; i++)
      if (!ignore[i] && IsThing(i)) {
      AnsiColor(objectansi[i]);
      ad = tan(planet2[i])*tan(DTOR(j));
      if (ad*ad > 1.0) {
        fprintf(S, " -- ");
        as1[i] = ds1[i] = ret2[i] = LARGE;
      } else {
        ad = ASIN(ad);
        oa = planet1[i]-ad;
        if (oa < 0.0)
          oa += 2.0*PI;
        am = oa-PI/2.0;
        if (am < 0.0)
          am += 2.0*PI;
        z = longm-am;
        if (z < 0.0)
          z += 2.0*PI;
        if (z > PI)
          z -= 2.0*PI;
        as1[i] = as[i];
        as[i] = z = RTOD(z);
        ret2[i] = ad;
        fprintf(S, "%3.0f%c", dabs(z), z < 0.0 ? 'e' : 'w');
      }
    }

    /* Again, the Descendant position is related to the Ascendant's,  */
    /* being a mirror image, so it can be calculated somewhat easier. */

    AnsiColor(DEFAULT);
    fprintf(S, "\nDsc@%2d%c: ", j >= 0 ? j : -j, j < 0 ? 's' : 'n');
    for (i = 1; i <= tot; i++)
      if (!ignore[i] && IsThing(i)) {
      AnsiColor(objectansi[i]);
      ad = ret2[i];
      if (ad == LARGE)
        fprintf(S, " -- ");
      else {
        od = planet1[i]+ad;
        dm = od+PI/2.0;
        z = longm-dm;
        if (z < 0.0)
          z += 2.0*PI;
        if (z > PI)
          z -= 2.0*PI;
        ds1[i] = ds[i];
        ds[i] = z = RTOD(z);
        fprintf(S, "%3.0f%c", dabs(z), z < 0.0 ? 'e' : 'w');
      }
    }
    printl();
#endif /* MATRIX */

    /* Now, if the -L0 switch is in effect, then take these line positions, */
    /* which we saved in an array above as we were printing them, and       */
    /* calculate and print the latitude crossings.                          */

    if (exdisplay & DASHL0)
      for (l = 1; l <= total; l++) if (!ignore[l] && IsThing(l))
        for (k = 1; k <= total; k++) {
          if (ignore[k] || !IsThing(k))
            continue;
          for (n = 0; n <= 1; n++) {
            x = n ? ds1[l] : as1[l];
            y = n ? ds[l] : as[l];
            for (m = 0; m <= 1; m++) {

            /* Check if Ascendant/Descendant cross Midheaven/Nadir. */

            z = m ? ic[k] : mc[k];
            if (occurcount < MAXCROSS &&
              dabs(x-y) < DEGHALF && Sgn(z-x) != Sgn(z-y)) {
              c->obj1[occurcount] = n ? -l : l;
              c->obj2[occurcount] = m ? -k : k;
              c->lat[occurcount] = (real)j+5.0*dabs(z-y)/dabs(x-y);
              c->lon[occurcount] = z;
              occurcount++;
            }

            /* Check if Ascendant/Descendant cross another Asc/Des. */

            w = m ? ds1[k] : as1[k];
            z = m ? ds[k] : as[k];
            if (occurcount < MAXCROSS && k > l &&
                dabs(x-y)+dabs(w-z) < DEGHALF && Sgn(w-x) != Sgn(z-y)) {
              c->obj1[occurcount] = n ? -l : l;
              c->obj2[occurcount] = 100+(m ? -k : k);
              c->lat[occurcount] = (real)j+5.0*
                dabs(y-z)/(dabs(x-w)+dabs(y-z));
              c->lon[occurcount] = MIN(x, y)+dabs(x-y)*
                dabs(y-z)/(dabs(x-w)+dabs(y-z));
              occurcount++;
            }
          }
        }
    }
  }
  if ((exdisplay & DASHL0) == 0)
    return;
  printl();

  /* Now, print out all the latitude crossings we found.  */
  /* First, we sort them in order of decreasing latitude. */

  for (i = 1; i < occurcount; i++) {
    j = i-1;
    while (j >= 0 && c->lat[j] < c->lat[j+1]) {
      SWAP(c->obj1[j], c->obj1[j+1]); SWAP(c->obj2[j], c->obj2[j+1]);
      SwapReal(&c->lat[j], &c->lat[j+1]); SwapReal(&c->lon[j], &c->lon[j+1]);
      j--;
    }
  }
  for (i = 1; i < occurcount; i++) {
    j = abs(c->obj1[i]);
    AnsiColor(objectansi[j]);
    fprintf(S, "%c%c%c ", OBJNAM(j));
    AnsiColor(elemansi[c->obj1[i] > 0 ? 0 : 2]);
    fprintf(S, "%s ", c->obj1[i] > 0 ? "Ascendant " : "Descendant");
    AnsiColor(WHITE);
    fprintf(S, "crosses ");
    j = abs(c->obj2[i] - (c->obj2[i] < 50 ? 0 : 100));
    AnsiColor(objectansi[j]);
    fprintf(S, "%c%c%c ", OBJNAM(j));
    AnsiColor(elemansi[c->obj2[i] < 50 ?
      (c->obj2[i] > 0 ? 1 : 3) : (c->obj2[i] > 100 ? 0 : 2)]);
    fprintf(S, "%s ", c->obj2[i] < 50 ? (c->obj2[i] > 0 ? "Midheaven " :
      "Nadir     ") : (c->obj2[i] > 100 ? "Ascendant " : "Descendant"));
    j = (int) (FRACT(dabs(c->lon[i]))*60.0);
    AnsiColor(DEFAULT);
    fprintf(S, "at %3d%c%02d'%c, ", (int) dabs(c->lon[i]), DEGR2,
      j, c->lon[i] < 0.0 ? 'E' : 'W');
    j = (int) (FRACT(dabs(c->lat[i]))*60.0);
    fprintf(S, "%2d%c%02d'%c\n", (int) dabs(c->lat[i]), DEGR2,
      j, c->lat[i] < 0.0 ? 'S' : 'N');
  }
  Deallocate(c);
  if (!occurcount) {
    AnsiColor(DEFAULT);
    fprintf(S, "No latitude crossings.\n");
  }
}

/* charts.c */
