/*
** Astrolog (Version 3.05) File: xgeneral.c
** Initially programmed 10/23-29/1991.
**
** 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.
**
** IMPORTANT: 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"

#ifdef GRAPH

/* Macros to access a point in a bitmap. */

#ifdef NOPC
#define AIND(B, X, Y) ((B)->m[(X) >> 1][(Y)])
#else
#define AIND(B, X, Y) ((B)[(long)(Y)*(long)(BITMAPX >> 1) + ((X) >> 1)])
#endif
#define PGET(B, X, Y) (AIND(B, X, Y) >> (((X)&1^1) << 2) & 15)
#define PSET(B, X, Y, O) AIND(B, X, Y) = AIND(B, X, Y) & \
  15 << (((X)&1) << 2) | (O) << (((X)&1^1) << 2)


/*
*******************************************************************************
** Core graphic procedures
*******************************************************************************
*/

/* Set a single point on the screen. This is the most basic graphic function */
/* and is called by all the more complex routines. Based on what mode we are */
/* in, we either set a cell in the bitmap array or a pixel on the window.    */

void DrawPoint(x, y, o)
int x, y;
bit o;
{
  if (xbitmap) {

    /* Force the coordinates to be within the bounds of the bitmap. */

    if (x < 0)
      x = 0;
    else if (x >= chartx)
      x = chartx-1;
    if (y < 0)
      y = 0;
    else if (y >= charty)
      y = charty-1;
    PSET(bm, x, y, o);
  }
#ifdef X11
  else
    XDrawPoint(disp, pixmap, gc, x, y);
#endif
#ifdef MSC
  else
    _setpixel(x, y);
#endif
}


/* Draw dot a little larger than just a single pixel at specified location. */

void DrawSpot(x, y, o)
int x, y;
bit o;
{
  DrawPoint(x, y, o);
  DrawPoint(x, y-1, o);
  DrawPoint(x-1, y, o);
  DrawPoint(x+1, y, o);
  DrawPoint(x, y+1, o);
}


/* Draw a filled in block, defined by the corners of its rectangle. */

void DrawBlock(x1, y1, x2, y2, o)
int x1, y1, x2, y2;
bit o;
{
  int x, y;
  SORT(y1, y2); SORT(x1, x2);
  if (xbitmap)
    for (y = y1; y <= y2; y++)         /* For bitmap, it's faster to  */
      for (x = x1; x <= x2; x++)       /* just fill in the array.     */
        PSET(bm, x, y, o);
#ifdef X11
  else
    for (y = y1; y <= y2; y++)         /* For X window, let's go call */
      DrawLine(x1, y, x2, y, o, 0);    /* XDrawLine for fewer events. */
#endif
#ifdef MSC
  else {
    Xcolor(o);
    _rectangle(_GFILLINTERIOR, x1, y1, x2, y2);
  }
#endif
}


/* Draw a rectangle on the screen with specified thickness. This is just   */
/* like DrawBlock() except that we are only drawing the edges of the area. */

void DrawBox(x1, y1, x2, y2, xsiz, ysiz, o)
int x1, y1, x2, y2, xsiz, ysiz;
bit o;
{
  DrawBlock(x1, y1, x2, y1 + ysiz - 1, o);
  DrawBlock(x1, y1 + ysiz, x1 + xsiz - 1, y2 - ysiz, o);
  DrawBlock(x2 - xsiz + 1, y1 + ysiz, x2, y2 - ysiz, o);
  DrawBlock(x1, y2 - ysiz + 1, x2, y2, o);
}


/* Draw a line on the screen, specified by its endpoints. In addition, we */
/* have specified a skip factor, which allows us to draw dashed lines.    */

void DrawLine(x1, y1, x2, y2, o, skip)
int x1, y1, x2, y2, skip;
bit o;
{
  int x = x1, y = y1, xadd, yadd, yinc, xabs, yabs, i, j = 0;

  if (skip < 0)
    skip = 0;
#ifdef X11
  if (!xbitmap) {
    Xcolor(o);

    /* For non-dashed X window lines, let's just have the Xlib do it for us. */

    if (!skip) {
      XDrawLine(disp, pixmap, gc, x1, y1, x2, y2);
      return;
    }
  }
#endif
#ifdef MSC
  if (!xbitmap) {
    Xcolor(o);

    /* For non-dashed lines, let's have the graphics library do it for us. */

    if (!skip) {
      _moveto(x1, y1);
      _lineto(x2, y2);
      return;
    }
  }
#endif
  xadd = x2 - x1 >= 0 ? 1 : 3;
  yadd = y2 - y1 >= 0 ? 2 : 4;
  xabs = abs(x2 - x1);
  yabs = abs(y2 - y1);

  /* Technically what we're doing here is drawing a line which is more    */
  /* horizontal then vertical. We always increment x by 1, and increment  */
  /* y whenever a fractional variable passes a certain amount. For lines  */
  /* that are more vertical than horizontal, we just swap x and y coords. */

  if (xabs < yabs) {
    SWAP(xadd, yadd);
    SWAP(xabs, yabs);
  }
  yinc = (xabs >> 1) - ((xabs & 1 ^ 1) && xadd > 2);
  for (i = xabs+1; i; i--) {
    if (j < 1)
      DrawPoint(x, y, o);
    j = j < skip ? j+1 : 0;
    switch (xadd) {
    case 1: x++; break;
    case 2: y++; break;
    case 3: x--; break;
    case 4: y--; break;
    }
    yinc += yabs;
    if (yinc - xabs >= 0) {
      yinc -= xabs;
      switch (yadd) {
      case 1: x++; break;
      case 2: y++; break;
      case 3: x--; break;
      case 4: y--; break;
      }
    }
  }
}


/* Draw a normal line on the screen; however, if the x coordinates are close */
/* to opposite sides of the window, then we assume that the line runs off    */
/* one side and reappears on the other, so draw the appropriate two lines    */
/* instead. This is used by the Ley line and astro-graph routines, which     */
/* draw lines running around the world and hence off the edges of the maps.  */

void DrawWrap(xold, yold, xnew, ynew, o)
int xold, yold, xnew, ynew;
bit o;
{
  int xmid, ymid, i;

  if (xold < 0) {
    DrawPoint(xnew, ynew, o);
    return;
  }
  xmid = 180*SCALE;

  /* If endpoints aren't near opposite edges, just draw the line and return. */

  if (dabs((real)(xnew-xold)) < (real) xmid) {
    DrawLine(xold, yold, xnew, ynew, o, 0);
    return;
  }
  i = xold < xmid ? xold+chartx-xnew-2 : xnew+chartx-xold-2;

  /* Determine vertical coordinate where our line runs off edges of screen. */

  ymid = yold+(int)((real)(ynew-yold)*
    (xold < xmid ? (real)(xold-1) : (real)(chartx-xold-2))/(real)i);
  DrawLine(xold, yold, xold < xmid ? 1 : chartx-2, ymid, o, 0);
  DrawLine(xnew < xmid ? 1 : chartx-2, ymid, xnew, ynew, o, 0);
}


/* This routine, and its companion below, clips a line defined by its */
/* endpoints to either above the y=0 line, or below some line y=c. By */
/* passing in parameters in different orders, we can clip to vertical */
/* lines, too. These are used by the DrawClip() routine below.        */

void ClipNegative(x1, y1, x2, y2)
int *x1, *y1, *x2, *y2;
{
  *x1 -= (int)((long)*y1*(*x2-*x1)/(*y2-*y1));
  *y1 = 0;
}

void ClipGreater(x1, y1, x2, y2, s)
int *x1, *y1, *x2, *y2, s;
{
  *x1 += (int)((long)(s-*y1)*(*x2-*x1)/(*y2-*y1));
  *y1 = s;
}


/* Draw a line on the screen. This is just like DrawLine() routine earlier; */
/* however, first clip the endpoints to the window viewport before drawing. */

void DrawClip(x1, y1, x2, y2, o, skip)
int x1, y1, x2, y2, skip;
bit o;
{
  if (x1 < 0)
    ClipNegative(&y1, &x1, &y2, &x2);    /* Check left side of window. */
  if (x2 < 0)
    ClipNegative(&y2, &x2, &y1, &x1);
  if (y1 < 0)
    ClipNegative(&x1, &y1, &x2, &y2);    /* Check top side of window.  */
  if (y2 < 0)
    ClipNegative(&x2, &y2, &x1, &y1);
  if (x1 > chartx)
    ClipGreater(&y1, &x1, &y2, &x2, chartx);    /* Check right of window.  */
  if (x2 > chartx)
    ClipGreater(&y2, &x2, &y1, &x1, chartx);
  if (y1 > charty)
    ClipGreater(&x1, &y1, &x2, &y2, charty);    /* Check bottom of window. */
  if (y2 > charty)
    ClipGreater(&x2, &y2, &x1, &y1, charty);
  DrawLine(x1, y1, x2, y2, o, skip);            /* Go draw the line.       */
}


/* Print a string of text on the graphic window at specified location. To do */
/* this we use Astrolog's own "font" and draw each letter separately.        */

void DrawText(string, x, y, o)
char *string;
int x, y;
color o;
{
  int s = scale;

  scale = 100;
  x++;
  y -= FONTY-3;
  while (*string) {
    DrawBlock(x, y, x+FONTX-1, y+FONTY-2, off);
    DrawTurtle(asciidraw[*string-' '], x, y, o);
    x += FONTX;
    string++;
  }
  scale = s;
}


/* Draw the glyph of an object at particular coordinates on the window. */

void DrawObject(i, x, y)
int i, x, y;
{
  char glyph[4];

  if (!label)    /* If we are inhibiting labels, then do nothing. */
    return;

  /* For other planet centered charts, we have to remember that that     */
  /* particular planet's index now represents the Earth. If we are given */
  /* that index to draw, then change it so we draw the Earth instead.    */

  if (modex != 's' &&
      ((i == centerplanet && i > 2) || (centerplanet == 0 && i == 1)))
    i = 0;
  if (i <= OBJECTS)
    DrawTurtle(objectdraw[i], x, y, objectcolor[i]);

  /* Normally we can just go draw the glyph; however, uranians and stars   */
  /* don't have glyphs, so for these draw their three letter abbreviation. */

  else {
    sprintf(glyph, "%c%c%c", OBJNAM(i));
    DrawText(glyph, x-StringLen(glyph)*FONTX/2, y+FONTY/2, objectcolor[i]);
  }
}


/* Convert a string segment to a positive number, updating the string to  */
/* point beyond the number chars. Return 1 if the string doesn't point to */
/* a numeric value. This is used by the DrawTurtle() routine to extract   */
/* motion vector quantities from draw strings, e.g. the "12" in "U12".    */

int IntInString(str)
char **str;
{
  int num = 0, i = 0;

  while (TRUE) {
    if (**str < '0' || **str > '9')
      return num > 0 ? num : (i < 1 ? 1 : 0);
    num = num*10+(**str)-'0';
    (*str)++;
    i++;
  }
}


/* This routine is used to draw complicated objects composed of lots of line */
/* segments on the screen, such as all the glyphs and coastline pieces. It   */
/* is passed in a string of commands defining what to draw in relative       */
/* coordinates. This is a copy of the format of the BASIC draw command found */
/* in PC's. For example, "U5R10D5L10" means go up 5 dots, right 10, down 5,  */
/* and left 10 - draw a box twice as wide as it is high.                     */

void DrawTurtle(lin, x0, y0, o)
char *lin;
int x0, y0;
bit o;
{
  int i, j, x, y, deltax, deltay, blank, noupdate;
  char cmd;

  turtlex = x0; turtley = y0;
#ifdef WIN
  if (!xbitmap)
    Xcolor(o);
#endif
  while (cmd = CAP(*lin)) {
    lin++;

    /* 'B' prefixing a command means just move the cursor, and don't draw. */

    if (blank = cmd == 'B') {
      cmd = CAP(*lin);
      lin++;
    }

    /* 'N' prefixing a command means don't update cursor when done drawing. */

    if (noupdate = cmd == 'N') {
      cmd = CAP(*lin);
      lin++;
    }

    /* Here we process the eight directional commands. */

    switch (cmd) {
    case 'U': deltax =  0; deltay = -1; break;    /* Up    */
    case 'D': deltax =  0; deltay =  1; break;    /* Down  */
    case 'L': deltax = -1; deltay =  0; break;    /* Left  */
    case 'R': deltax =  1; deltay =  0; break;    /* Right */
    case 'E': deltax =  1; deltay = -1; break;    /* Northeast */
    case 'F': deltax =  1; deltay =  1; break;    /* Southeast */
    case 'G': deltax = -1; deltay =  1; break;    /* Southwest */
    case 'H': deltax = -1; deltay = -1; break;    /* Northwest */
    default:                                      /* Shouldn't happen. */
      fprintf(stderr, "%s: Bad turtle subcommand: '%c'\n", appname, cmd);
      Terminate(1);
    }
    x = turtlex;
    y = turtley;
    j = IntInString(&lin)*SCALE;    /* Figure out how far to draw. */
    if (blank) {
      turtlex += deltax*j;
      turtley += deltay*j;
    } else {
      DrawPoint(turtlex, turtley, o);
      for (i = 0; i < j; i++) {
        turtlex += deltax;
        turtley += deltay;
        DrawPoint(turtlex, turtley, o);
      }
      if (noupdate) {
        turtlex = x;
        turtley = y;
      }
    }
  }
}


/*
*******************************************************************************
** Bitmap file routines
*******************************************************************************
*/

#define INTTOHEX(I) (char) ((I) < 10 ? '0' + (I) : 'a' + (I) - 10)

/* Write the bitmap array to a previously opened file in a format that   */
/* can be read in by the Unix X commands bitmap and xsetroot. The 'mode' */
/* parameter defines how much white space is put in the file.            */

void WriteXBitmap(data, name, mode)
FILE *data;
char *name, mode;
{
  int x, y, i, value, temp = 0;

  fprintf(data, "#define %s_width %d\n" , name, chartx);
  fprintf(data, "#define %s_height %d\n", name, charty);
  fprintf(data, "static %s %s_bits[] = {",
    mode != 'V' ? "char" : "short", name);
  for (y = 0; y < charty; y++) {
    x = 0;
    do {

      /* Process each row, eight columns at a time. */

      if (y + x > 0)
        fprintf(data, ",");
      if (temp == 0)
        fprintf(data, "\n%s",
          mode == 'N' ? "  " : (mode == 'C' ? " " : ""));
      value = 0;
      for (i = (mode != 'V' ? 7 : 15); i >= 0; i--)
        value = (value << 1) +
          (!(PGET(bm, x+i, y)^(xreverse*15))^xreverse && (x + i < chartx));
      if (mode == 'N')
        putc(' ', data);
      fprintf(data, "0x");
      if (mode == 'V')
        fprintf(data, "%c%c",
          INTTOHEX(value >> 12), INTTOHEX((value >> 8) & 15));
      fprintf(data, "%c%c",
        INTTOHEX((value >> 4) & 15), INTTOHEX(value & 15));
      temp++;

      /* Is it time to skip to the next line while writing the file yet? */

      if ((mode == 'N' && temp >= 12) ||
          (mode == 'C' && temp >= 15) ||
          (mode == 'V' && temp >= 11))
        temp = 0;
      x += (mode != 'V' ? 8 : 16);
    } while (x < chartx);
  }
  fprintf(data, "};\n");
}


/* Write the bitmap array to a previously opened file in a simple boolean    */
/* Ascii rectangle, one char per pixel, where '#' represents an off bit and  */
/* '-' an on bit. The output format is identical to the format generated by  */
/* the Unix bmtoa command, and it can be converted into a bitmap with atobm. */

void WriteAscii(data)
FILE *data;
{
  int x, y, i;

  for (y = 0; y < charty; y++) {
    for (x = 0; x < chartx; x++) {
      i = PGET(bm, x, y);
      if (xcolor)
        putc(INTTOHEX(i), data);
      else
        putc(i ? '-' : '#', data);
    }
    putc('\n', data);
  }
}


#define putbyte(A) putc((byte) (A), data)
#define putword(A) putbyte(LOBYTE(A)); putbyte(HIBYTE(A))
#define putlong(A) putword(LOWORD(A)); putword(HIWORD(A))

/* Write the bitmap array to a previously opened file in the bitmap format  */
/* used in Microsoft Windows for its .bmp extension files. This is a pretty */
/* efficient format, only requiring one bit per pixel and a small header.   */

void WriteBmp(data)
FILE *data;
{
  int x, y;
  unsigned long value;

  /* BitmapFileHeader */
  putbyte('B'); putbyte('M');
  putlong(14+40 + (xcolor ? 64 : 8) +
    (long)4*charty*((chartx-1 >> (xcolor ? 3 : 5))+1));
  putword(0); putword(0);
  putlong(14+40 + (xcolor ? 64 : 8));
  /* BitmapInfo / BitmapInfoHeader */
  putlong(40);
  putlong(chartx); putlong(charty);
  putword(1); putword(xcolor ? 4 : 1);
  putlong(0 /*BI_RGB*/); putlong(0);
  putlong(chartx*10); putlong(charty*10);
  putlong(0); putlong(0);
  /* RgbQuad */
  if (xcolor)
    for (x = 0; x < 16; x++) {
      putbyte(RGBB(rgbbmp[x])); putbyte(RGBG(rgbbmp[x]));
      putbyte(RGBR(rgbbmp[x])); putbyte(0);
    }
  else {
    putlong(0);
    putbyte(255); putbyte(255); putbyte(255); putbyte(0);
  }
  /* Data */
  for (y = charty-1; y >= 0; y--) {
    value = 0;
    for (x = 0; x < chartx; x++) {
      if ((x & (xcolor ? 7 : 31)) == 0 && x > 0) {
        putlong(value);
        value = 0;
      }
      if (xcolor)
        value |= (dword)PGET(bm, x, y) << ((x & 7 ^ 1) << 2);
      else
        if (PGET(bm, x, y))
          value |= (long)1 << (x & 31 ^ 7);
    }
    putlong(value);
  }
}


/* Output the bitmap in memory to a file. This basically consists of just    */
/* calling the routine to actually write a bitmap to a file, although we     */
/* need to prompt the user for a filename if it wasn't specified beforehand. */

void WriteFile()
{
  FILE *data;
  int tty;

  tty = (outputfile[0] == 't' && outputfile[1] == 't' &&
    outputfile[2] == 'y' && outputfile[3] == 0);
  while (TRUE) {
    if (tty) {
      printf("Enter name of file to write X bitmap to > ");
      if (gets(outputfile) == (char *) NULL) {
        printf("\n%s terminated.\n", appname);
        Terminate(2);
      }
    }
    data = fopen(outputfile, "wb");
    if (data != NULL)
      break;
    else {
      printf("Bad output file.\n");
      tty = TRUE;
    }
  }
  if (bitmapmode == 'B')
    WriteBmp(data);
  else if (bitmapmode == 'A')
    WriteAscii(data);
  else
    WriteXBitmap(data, outputfile, bitmapmode);
  fclose(data);
}
#endif

/* xgeneral.c */
