/***********************************************************************/
/*                                                                     */
/*                           Objective Caml                            */
/*                                                                     */
/*             Damien Doligez, projet Para, INRIA Rocquencourt         */
/*                                                                     */
/*  Copyright 1998 Institut National de Recherche en Informatique et   */
/*  en Automatique.  All rights reserved.  This file is distributed    */
/*  under the terms of the GNU Library General Public License, with    */
/*  the special exception on linking described in file ../LICENSE.     */
/*                                                                     */
/***********************************************************************/

/* $Id: graph.c,v 1.17 2001/12/07 13:39:46 xleroy Exp $ */

#include "alloc.h"
#include "callback.h"
#include "fail.h"
#include "memory.h"
#include "mlvalues.h"
#include "signals.h"

#include "main.h"      /* Include main.h last or Assert will not work. */


/* The off-screen buffer that holds the contents of the graphics arena. */
static GWorldPtr gworld = NULL;

/* An arbitrarily large rectangle (for clipping). */
static Rect maxrect = { -SHRT_MAX, -SHRT_MAX, SHRT_MAX, SHRT_MAX };

/* Coordinates (relative to the window) of the top-left corner
   of the graphics arena. */
long x0, y0;

/* Width and height of the graphics arena. */
long w0, h0;

RGBColor fgcolor;

/* Convert from Caml coordinates to QD coordinates in the off-screen buffer. */
/* Note: these conversions are self-inverse (see gr_current_point). */
#define Bx(x) (x)
#define By(y) (h0-1 - (y))

/* Convert from Caml coordinates to QD coordinates in the window. */
#define Wx(x) (Bx(x) + x0)
#define Wy(y) (By(y) + y0)

/* Convert from QD window coordinates to Caml coordinates. */
#define Cx(x) ((x) - x0)
#define Cy(y) (h0-1 - ((y) - y0))


/***********************************************************************/
/* User interface functions                                            */
/***********************************************************************/

static void GraphUpdateGW (void)
{
  Rect r;
  WStatusH st = WinGetStatus (winGraphics);

  Assert (st != NULL);
  Assert (gworld != NULL);
  WELongRectToRect (&(*st)->destrect, &r);
  OffsetRect (&r, winGraphics->portRect.left, winGraphics->portRect.top);
  UpdateGWorld (&gworld, 0, &r, NULL, NULL, clipPix);
}

void GraphNewSizePos (void)
{
  GraphUpdateGW ();
}

/* The current port must be winGraphics when this function is called. */
void GraphUpdate (void)
{
  Rect r, src, dst;
  Boolean good;
  WStatusH st = WinGetStatus (winGraphics);
  RGBColor forecolor, backcolor;

  Assert (st != NULL);
  GraphUpdateGW ();
  good = LockPixels (GetGWorldPixMap (gworld));   Assert (good);
  WELongRectToRect (&(*st)->destrect, &r);
  WELongRectToRect (&(*st)->viewrect, &dst);
  src = dst;
  OffsetRect (&src, -r.left, -r.top);
  GetBackColor (&backcolor);
  GetForeColor (&forecolor);
  BackColor (whiteColor);
  ForeColor (blackColor);
  CopyBits (&((GrafPtr) gworld)->portBits, &((GrafPtr) winGraphics)->portBits,
            &src, &dst, srcCopy, NULL);
  RGBBackColor (&backcolor);
  RGBForeColor (&forecolor);
  UnlockPixels (GetGWorldPixMap (gworld));
}

/* All scrolling of the graphics window must go through this function
   so it can update the coordinates x0 and y0, and the pen location. */
void GraphScroll (long dx, long dy)
{
  Rect r;
  RgnHandle update = NewRgn ();
  WStatusH st = WinGetStatus (winGraphics);
  Point p;
  GrafPtr port;

  Assert (st != NULL);
  GetPort (&port);
  SetPort (winGraphics);
  WELongRectToRect (&(*st)->viewrect, &r);
  ScrollRect (&r, dx, dy, update);
  WEOffsetLongRect (&(*st)->destrect, dx, dy);
  SetClip (update);
  GraphUpdate ();
  ClipRect (&maxrect);
  DisposeRgn (update);

  x0 += dx;
  y0 += dy;
  GetPen (&p);
  MoveTo (p.h + dx, p.v + dy);
  SetPort (port);
}

/* Graphics event queue */
#define GraphQsize 15
static EventRecord graphQ[GraphQsize];
static int graphQlen = 0;

#define Succ(x) ((x) >= GraphQsize ? 0 : (x)+1)

void GraphGotEvent (EventRecord *evt)
{
  GrafPort *saveport;

  if (graphQlen < GraphQsize) ++ graphQlen;
  memmove (&(graphQ[1]), &(graphQ[0]), (graphQlen - 1) * sizeof (graphQ[0]));

  graphQ[0] = *evt;

  PushWindowPort (winGraphics);
  GlobalToLocal (&(graphQ[0].where));
  PopPort;
}
static void DequeueEvent (int i)
{
  -- graphQlen;
  memmove (&(graphQ[i]), &(graphQ[i+1]), (graphQlen - i) * sizeof (graphQ[0]));
}

/***********************************************************************/
/* Primitives for the graphics library                                 */
/***********************************************************************/

value gr_open_graph (value vgeometry);
value gr_close_graph (value unit);
value gr_sigio_signal (value unit);
value gr_sigio_handler (value unit);
value gr_display_mode (value flag);
value gr_remember_mode (value flag);
value gr_synchronize (value unit);
value gr_clear_graph (value unit);
value gr_size_x (value unit);
value gr_size_y (value unit);
value gr_set_color (value vrgb);
value gr_plot (value vx, value vy);
value gr_point_color (value vx, value vy);
value gr_moveto (value vx, value vy);
value gr_current_x (value unit);
value gr_current_y (value unit);
value gr_lineto (value vx, value vy);
value gr_draw_rect (value vx, value vy, value vw, value vh);
value gr_draw_arc (value *argv, int argc);
value gr_draw_arc_nat (value, value, value, value, value, value);
value gr_set_line_width (value vwidth);
value gr_fill_rect (value vx, value vy, value vw, value vh);
value gr_fill_poly (value vpoints);
value gr_fill_arc (value *argv, int argc);
value gr_fill_arc_nat (value, value, value, value, value, value);
value gr_draw_char (value vchr);
value gr_draw_string (value vstr);
value gr_set_font (value vfontname);
value gr_set_text_size (value vsz);
value gr_text_size (value vstr);
value gr_make_image (value varray);
value gr_dump_image (value vimage);
value gr_draw_image (value vimage, value vx, value vy);
value gr_create_image (value vw, value vh);
value gr_blit_image (value vimage, value vx, value vy);
value gr_wait_event (value veventlist);
value gr_sound (value vfreq, value vdur);
value gr_set_window_title (value title);

#define UNIMPLEMENTED(f, args) \
value f args; \
value f args \
{ \
  failwith ("not implemented: " #f); \
  return Val_unit;  /* not reached */ \
}

UNIMPLEMENTED (gr_window_id, (value unit))
UNIMPLEMENTED (gr_open_subwindow, (value x, value y, value w, value h))
UNIMPLEMENTED (gr_close_subwindow, (value id))


/**** Ancillary macros and function */

/* double-buffer or write-through */
static int grdisplay_mode;
static int grremember_mode;

/* Current state */
static long cur_x, cur_y;
static short cur_width, cur_font, cur_size;
/* see also fgcolor */


/* Drawing off-screen and on-screen simultaneously.  The following three
   macros must always be used together and in this order.
*/
/* 1. Begin drawing in the off-screen buffer. */
#define BeginOff { \
  CGrafPtr _saveport_; \
  GDHandle _savegdev_; \
  Rect _cliprect_; \
  if (grremember_mode) { \
    GetGWorld (&_saveport_, &_savegdev_); \
    LockPixels (GetGWorldPixMap (gworld)); \
    SetGWorld ((CGrafPtr) gworld, NULL);

/* 2. Continue with on-screen drawing. */
#define On \
    SetGWorld (_saveport_, _savegdev_); \
    UnlockPixels (GetGWorldPixMap (gworld)); \
  } \
  if (grdisplay_mode) { \
    SetPort (winGraphics); \
    ScrollCalcGraph (winGraphics, &_cliprect_); \
    ClipRect (&_cliprect_);

/* 3. Clean up after drawing. */
#define EndOffOn \
    ClipRect (&maxrect); \
    SetPort ((GrafPtr) _saveport_); \
  } \
}

/* Set up the current port unconditionally.  This is for functions that
   don't draw (measurements and setting the graphport state).
   Usage: BeginOffAlways / EndOffAlways
      or  BeginOffAlways / OnAlways / EndOffOnAlways
   */
#define BeginOffAlways { \
  CGrafPtr _saveport_; \
  GDHandle _savegdev_; \
  GetGWorld (&_saveport_, &_savegdev_); \
  LockPixels (GetGWorldPixMap (gworld)); \
  SetGWorld ((CGrafPtr) gworld, NULL);

#define EndOffAlways \
  SetGWorld (_saveport_, _savegdev_); \
  UnlockPixels (GetGWorldPixMap (gworld)); \
}

#define OnAlways \
  SetGWorld (_saveport_, _savegdev_); \
  UnlockPixels (GetGWorldPixMap (gworld)); \
  SetPort (winGraphics); \

#define EndOffOnAlways \
  SetPort ((GrafPtr) _saveport_); \
}

/* Convert a red, green, or blue value from 8 bits to 16 bits. */
#define RGB8to16(x) ((x) | ((x) << 8))

/* Declare and convert x and y from vx and vy. */
#define XY long x = Long_val (vx), y = Long_val (vy)


static value * graphic_failure_exn = NULL;

static void gr_fail(char *fmt, void *arg)
{
  char buffer[1024];

  if (graphic_failure_exn == NULL) {
    graphic_failure_exn = caml_named_value("Graphics.Graphic_failure");
    if (graphic_failure_exn == NULL){
      invalid_argument("Exception Graphics.Graphic_failure not initialized,"
                       " you must load graphics.cma");
    }
  }
  sprintf(buffer, fmt, arg);
  raise_with_string(*graphic_failure_exn, buffer);
}

static void gr_check_open (void)
{
  if (winGraphics == NULL) gr_fail("graphic screen not opened", NULL);
}

/* Max_image_mem is the number of image pixels that can be allocated
   in one major GC cycle.  The GC will speed up to match this allocation
   speed.
*/
#define Max_image_mem 1000000   /*FIXME Should use user pref. */

#define Transparent (-1)

struct grimage {
  final_fun f;                  /* Finalization function */
  long width, height;           /* Dimensions of the image */
  GWorldPtr data;               /* Pixels */
  GWorldPtr mask;               /* Mask for transparent points, or NULL */
};

#define Grimage_wosize \
  ((sizeof (struct grimage) + sizeof (value) - 1) / sizeof (value))

static void free_image (value vimage)
{
  struct grimage *im = (struct grimage *) Bp_val (vimage);

  if (im->data != NULL) DisposeGWorld (im->data);
  if (im->mask != NULL) DisposeGWorld (im->mask);
}

static value alloc_image (long w, long h)
{
  value res = alloc_final (Grimage_wosize, free_image, w*h, Max_image_mem);
  struct grimage *im = (struct grimage *) Bp_val (res);
  Rect r;
  QDErr err;

  im->width = w;
  im->height = h;
  im->mask = NULL;
  SetRect (&r, 0, 0, w, h);
  err = NewGWorld (&im->data, 32, &r, NULL, NULL, 0);
  if (err != noErr){
    im->data = NULL;
    gr_fail ("Cannot allocate image (error code %ld)", (void *) err);
  }
  return res;
}

static value gr_alloc_int_vect(mlsize_t size)
{
  value res;
  mlsize_t i;

  if (size <= Max_young_wosize) {
    res = alloc(size, 0);
  } else {
    res = alloc_shr(size, 0);
  }
  for (i = 0; i < size; i++) {
    Field(res, i) = Val_long(0);
  }
  return res;
}

/***********************************************************************/

value gr_open_graph (value vgeometry)
{
  int i;
  short err;
  Rect r;
  WStatusH st;

  if (winGraphics == NULL){
    Assert (gworld == NULL);

    i = sscanf (String_val (vgeometry), "%ldx%ld", &w0, &h0);
    if (i < 2){
      w0 = 640;
      h0 = 480;
    }
    if (w0 < kMinWindowWidth - kScrollBarWidth - 1){
      w0 = kMinWindowWidth - kScrollBarWidth - 1;
    }
    if (h0 < kMinWindowHeight - kScrollBarWidth - 1){
      h0 = kMinWindowHeight - kScrollBarWidth - 1;
    }

    err = WinOpenGraphics (w0, h0);
    if (err != noErr) goto failed;

    x0 = y0 = 0;

    st = WinGetStatus (winGraphics);   Assert (st != NULL);
    WELongRectToRect (&(*st)->destrect, &r);
    OffsetRect (&r, winGraphics->portRect.left, winGraphics->portRect.top);
    err = NewGWorld (&gworld, 0, &r, NULL, NULL, 0);
    if (err != noErr) goto failed;

    fgcolor.red = fgcolor.green = fgcolor.blue = 0;
  }
  /* Synchronise off-screen and on-screen by initialising everything. */
  grremember_mode = 1;
  grdisplay_mode = 1;
  gr_clear_graph (Val_unit);
  gr_moveto (Val_long (0), Val_long (0));
  gr_set_color (Val_long (0));
  gr_set_line_width (Val_long (0));
  gr_set_font ((value) "geneva");           /* XXX hack */
  gr_set_text_size (Val_long (12));

  return Val_unit;

  failed:
    if (gworld != NULL){
      DisposeGWorld (gworld);
      gworld = NULL;
    }
    if (winGraphics != NULL) WinCloseGraphics ();
    gr_fail ("open_graph failed (error %d)", (void *) (long) err);
    return Val_unit; /* not reached */
}

value gr_close_graph (value unit)
{
#pragma unused (unit)
  gr_check_open ();
  WinCloseGraphics ();
  DisposeGWorld (gworld);
  gworld = NULL;
  return Val_unit;
}

value gr_sigio_signal (value unit)            /* Not used on MacOS */
{
#pragma unused (unit)
  return Val_unit;
}

value gr_sigio_handler (value unit)           /* Not used on MacOS */
{
#pragma unused (unit)
  return Val_unit;
}

value gr_synchronize (value unit)
{
#pragma unused (unit)
  GrafPtr saveport;
  
  gr_check_open ();
  PushWindowPort (winGraphics);
  GraphUpdate ();
  PopPort;
  return Val_unit;
}

value gr_display_mode (value flag)
{
  grdisplay_mode = Bool_val (flag);
  return Val_unit;
}

value gr_remember_mode (value flag)
{
  grremember_mode = Bool_val (flag);
  return Val_unit;
}

value gr_clear_graph (value unit)
{
#pragma unused (unit)
  gr_check_open ();
  BeginOff
    EraseRect (&maxrect);
  On
    EraseRect (&maxrect);
  EndOffOn
  return unit;
}

value gr_size_x (value unit)
{
#pragma unused (unit)
  gr_check_open ();
  return Val_long (w0);
}

value gr_size_y (value unit)
{
#pragma unused (unit)
  gr_check_open ();
  return Val_long (h0);
}

value gr_set_color (value vrgb)
{
  long rgb = Long_val (vrgb);

  gr_check_open ();
  fgcolor.red = RGB8to16 ((rgb >> 16) & 0xFF);
  fgcolor.green = RGB8to16 ((rgb >> 8) & 0xFF);
  fgcolor.blue = RGB8to16 (rgb & 0xFF);
  BeginOffAlways
    RGBForeColor (&fgcolor);
  OnAlways
    RGBForeColor (&fgcolor);
  EndOffOnAlways
  return Val_unit;
}

value gr_plot (value vx, value vy)
{
  XY;

  gr_check_open ();
  BeginOff
    SetCPixel (Bx (x), By (y) - 1, &fgcolor);
  On
    SetCPixel (Wx (x), Wy (y) - 1, &fgcolor);
  EndOffOn
  return Val_unit;
}

value gr_point_color (value vx, value vy)
{
  XY;
  RGBColor c;

  gr_check_open ();
  if (x < 0 || x >= w0 || y < 0 || y >= h0) return Val_long (-1);
  BeginOffAlways
    GetCPixel (Bx (x), By (y) - 1, &c);
  EndOffAlways
  return Val_long (((c.red & 0xFF00) << 8)
                   | (c.green & 0xFF00)
                   | ((c.blue & 0xFF00) >> 8));
}

value gr_moveto (value vx, value vy)
{
  XY;

  gr_check_open ();
  cur_x = x; cur_y = y;
  return Val_unit;
}

value gr_current_x (value unit)
{
#pragma unused (unit)

  gr_check_open ();
  return Val_long (cur_x);
}

value gr_current_y (value unit)
{
#pragma unused (unit)

  gr_check_open ();
  return Val_long (cur_y);
}

value gr_lineto (value vx, value vy)
{
  XY;
  int delta = cur_width / 2;

  gr_check_open ();
  BeginOff
    MoveTo (Bx (cur_x) - delta, By (cur_y) - delta);
    LineTo (Bx (x) - delta, By (y) - delta);
  On
    MoveTo (Wx (cur_x) - delta, Wy (cur_y) - delta);
    LineTo (Wx (x) - delta, Wy (y) - delta);
  EndOffOn
  cur_x = x; cur_y = y;
  return Val_unit;
}

value gr_draw_rect (value vx, value vy, value vw, value vh)
{
  XY;
  long w = Long_val (vw), h = Long_val (vh);
  Rect r;
  int d1 = cur_width / 2;
  int d2 = cur_width - d1;

  gr_check_open ();
  BeginOff
    SetRect (&r, Bx (x) - d1, By (y+h) - d1, Bx (x+w) + d2, By (y) + d2);
    FrameRect (&r);
  On
    SetRect (&r, Wx (x) - d1, Wy (y+h) - d1, Wx (x+w) + d2, Wy (y) + d2);
    FrameRect (&r);
  EndOffOn
  return Val_unit;
}

value gr_draw_arc (value *argv, int argc)
{
#pragma unused (argc)
  return gr_draw_arc_nat (argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]);
}

value gr_draw_arc_nat (value vx, value vy, value vrx, value vry, value va1,
                       value va2)
{
  XY;
  long rx = Long_val (vrx), ry = Long_val (vry);
  long a1 = Long_val (va1), a2 = Long_val (va2);
  Rect r;
  long qda1 = 90 - a1, qda2 = 90 - a2;
  int d1 = cur_width / 2;
  int d2 = cur_width - d1;

  gr_check_open ();
  BeginOff
    SetRect (&r, Bx(x-rx) - d1, By(y+ry) - d1, Bx(x+rx) + d2, By(y-ry) + d2);
    FrameArc (&r, qda1, qda2 - qda1);
  On
    SetRect (&r, Wx(x-rx) - d1, Wy(y+ry) - d1, Wx(x+rx) + d2, Wy(y-ry) + d2);
    FrameArc (&r, qda1, qda2 - qda1);
  EndOffOn
  return Val_unit;
}

value gr_set_line_width (value vwidth)
{
  short width = Int_val (vwidth);

  if (width == 0) width = 1;
  gr_check_open ();
  BeginOffAlways
    PenSize (width, width);
  OnAlways
    PenSize (width, width);
  EndOffOnAlways
  cur_width = width;
  return Val_unit;
}

value gr_fill_rect (value vx, value vy, value vw, value vh)
{
  XY;
  long w = Long_val (vw), h = Long_val (vh);
  Rect r;

  gr_check_open ();
  BeginOff
    SetRect (&r, Bx (x), By (y+h), Bx (x+w), By (y));
    PaintRect (&r);
  On
    SetRect (&r, Wx (x), Wy (y+h), Wx (x+w), Wy (y));
    PaintRect (&r);
  EndOffOn
  return Val_unit;
}

value gr_fill_poly (value vpoints)
{
  long i, n = Wosize_val (vpoints);
  PolyHandle p;

  #define Bxx(i) Bx (Int_val (Field (Field (vpoints, (i)), 0)))
  #define Byy(i) By (Int_val (Field (Field (vpoints, (i)), 1)))

  gr_check_open ();
  if (n < 1) return Val_unit;

  p = OpenPoly ();
  MoveTo (Bxx (0), Byy (0));
  for (i = 1; i < n; i++) LineTo (Bxx (i), Byy (i));
  ClosePoly ();
  BeginOff
    PaintPoly (p);
  On
    OffsetPoly (p, x0, y0);
    PaintPoly (p);
  EndOffOn
  KillPoly (p);
  return Val_unit;
}

value gr_fill_arc (value *argv, int argc)
{
#pragma unused (argc)
  return gr_fill_arc_nat (argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]);
}

value gr_fill_arc_nat (value vx, value vy, value vrx, value vry, value va1,
                       value va2)
{
  XY;
  long rx = Long_val (vrx), ry = Long_val (vry);
  long a1 = Long_val (va1), a2 = Long_val (va2);
  Rect r;
  long qda1 = 90 - a1, qda2 = 90 - a2;

  gr_check_open ();
  BeginOff
    SetRect (&r, Bx (x-rx), By (y+ry), Bx (x+rx), By (y-ry));
    PaintArc (&r, qda1, qda2 - qda1);
  On
    SetRect (&r, Wx (x-rx), Wy (y+ry), Wx (x+rx), Wy (y-ry));
    PaintArc (&r, qda1, qda2 - qda1);
  EndOffOn
  return Val_unit;
}

static void draw_text (char *txt, unsigned long len)
{
  FontInfo info;
  unsigned long w;
  
  if (len > 32767) len = 32767;

  BeginOffAlways
    GetFontInfo (&info);
    w = TextWidth (txt, 0, len);
  EndOffAlways

  gr_check_open ();
  BeginOff
    MoveTo (Bx (cur_x), By (cur_y) - info.descent);
    DrawText (txt, 0, len);
  On
    MoveTo (Wx (cur_x), Wy (cur_y) - info.descent);
    DrawText (txt, 0, len);
  EndOffOn
  cur_x += w;
}

value gr_draw_char (value vchr)
{
  char c = Int_val (vchr);

  draw_text (&c, 1);
  return Val_unit;
}

value gr_draw_string (value vstr)
{
  mlsize_t len = string_length (vstr);
  char *str = String_val (vstr);

  draw_text (str, len);
  return Val_unit;
}

value gr_set_font (value vfontname)
{
  Str255 pfontname;
  short fontnum;

  gr_check_open ();
  CopyCStringToPascal (String_val (vfontname), pfontname);
  GetFNum (pfontname, &fontnum);
  BeginOffAlways
    TextFont (fontnum);
  OnAlways
    TextFont (fontnum);
  EndOffOnAlways
  cur_font = fontnum;
  return Val_unit;
}

value gr_set_text_size (value vsz)
{
  short sz = Int_val (vsz);

  gr_check_open ();
  BeginOffAlways
    TextSize (sz);
  OnAlways
    TextSize (sz);
  EndOffOnAlways
  cur_size = sz;
  return Val_unit;
}

value gr_text_size (value vstr)
{
  mlsize_t len = string_length (vstr);
  char *str = String_val (vstr);
  value result = alloc_tuple (2);
  FontInfo info;
  long w, h;

  BeginOffAlways
    GetFontInfo (&info);
    w = TextWidth (str, 0, len);
    h = info.ascent + info.descent;
  EndOffAlways
  Field (result, 0) = Val_long (w);
  Field (result, 1) = Val_long (h);
  return result;
}

value gr_make_image (value varray)
{
  long height = Wosize_val (varray);
  long width;
  long x, y;
  GWorldPtr w;
  value result, line;
  long color;
  RGBColor qdcolor;
  int has_transp = 0;
  CGrafPtr saveport;
  GDHandle savegdev;

  gr_check_open ();
  if (height == 0) return alloc_image (0, 0);
  width = Wosize_val (Field (varray, 0));
  for (y = 1; y < height; y++){
    if (Wosize_val (Field (varray, y)) != width){
      gr_fail("make_image: lines of different lengths", NULL);
    }
  }

  result = alloc_image (width, height);
  w = ((struct grimage *) Bp_val (result))->data;

  LockPixels (GetGWorldPixMap (w));
  GetGWorld (&saveport, &savegdev);
  SetGWorld ((CGrafPtr) w, NULL);
  for (y = 0; y < height; y++){
    line = Field (varray, y);
    for (x = 0; x < width; x++){
      color = Long_val (Field (line, x));
      if (color == Transparent) has_transp = 1;
      qdcolor.red = ((color >> 16) & 0xFF) | ((color >> 8) & 0xFF00);
      qdcolor.green = ((color >> 8) & 0xFF) | (color & 0xFF00);
      qdcolor.blue = (color & 0xFF) | ((color << 8) & 0xFF00);
      SetCPixel (x, y, &qdcolor);
    }
  }
  UnlockPixels (GetGWorldPixMap (w));

  if (has_transp){
    Rect r;
    QDErr err;

    SetRect (&r, 0, 0, width, height);
    err = NewGWorld (&w, 1, &r, NULL, NULL, 0);
    if (err != noErr){
      SetGWorld (saveport, savegdev);
      gr_fail ("Cannot allocate image (error code %d)", (void *) err);
    }
    LockPixels (GetGWorldPixMap (w));
    SetGWorld ((CGrafPtr) w, NULL);
    EraseRect (&maxrect);
    qdcolor.red = qdcolor.green = qdcolor.blue = 0;
    for (y = 0; y < height; y++){
      line = Field (varray, y);
      for (x = 0; x < width; x++){
        color = Long_val (Field (line, x));
        if (color != Transparent) SetCPixel (x, y, &qdcolor);
      }
    }
    UnlockPixels (GetGWorldPixMap (w));
    ((struct grimage *) Bp_val (result))->mask = w;
  }

  SetGWorld (saveport, savegdev);

  return result;
}

value gr_dump_image (value vimage)
{
  value result = Val_unit;
  struct grimage *im = (struct grimage *) Bp_val (vimage);
  long width = im->width;
  long height = im->height;
  long x, y;
  GWorldPtr wdata = im->data;
  GWorldPtr wmask = im->mask;
  CGrafPtr saveport;
  GDHandle savegdev;
  RGBColor qdcolor;
  value line;

  gr_check_open ();
  Begin_roots2 (vimage, result);
    result = gr_alloc_int_vect (height);
    for (y = 0; y < height; y++){
      value v = gr_alloc_int_vect (width);
      modify (&Field (result, y), v);
    }
  End_roots ();
  GetGWorld (&saveport, &savegdev);
  LockPixels (GetGWorldPixMap (wdata));
  SetGWorld (wdata, NULL);
  for (y = 0; y < height; y++){
    line = Field (result, y);
    for (x = 0; x < width; x++){
      GetCPixel (x, y, &qdcolor);
      Field (line, x) = Val_long (((qdcolor.red & 0xFF00) << 8)
                                  | (qdcolor.green & 0xFF00)
                                  | ((qdcolor.blue & 0xFF00) >> 8));
    }
  }
  UnlockPixels (GetGWorldPixMap (wdata));
  if (wmask != NULL){
    LockPixels (GetGWorldPixMap (wmask));
    SetGWorld (wmask, NULL);
    for (y = 0; y < height; y++){
      line = Field (result, y);
      for (x = 0; x < width; x++){
        if (!GetPixel (x, y)) Field (line, x) = Val_long (Transparent);
      }
    }
    UnlockPixels (GetGWorldPixMap (wmask));
  }
  SetGWorld (saveport, savegdev);
  return result;
}

value gr_draw_image (value vimage, value vx, value vy)
{
  XY;
  struct grimage *im = (struct grimage *) Bp_val (vimage);
  RGBColor forecolor, backcolor;
  Rect srcrect, dstrect;

  SetRect (&srcrect, 0, 0, im->width, im->height);
  if (im->mask != NULL){
    LockPixels (GetGWorldPixMap (im->data));
    LockPixels (GetGWorldPixMap (im->mask));
    BeginOff
      SetRect (&dstrect, Bx (x), By (y+im->height), Bx (x+im->width), By (y));
      GetBackColor (&backcolor);
      GetForeColor (&forecolor);
      BackColor (whiteColor);
      ForeColor (blackColor);
      CopyMask (&((GrafPtr) im->data)->portBits,
                &((GrafPtr) im->mask)->portBits,
                &((GrafPtr) gworld)->portBits,
                &srcrect, &srcrect, &dstrect);
      RGBBackColor (&backcolor);
      RGBForeColor (&forecolor);
    On
      SetRect (&dstrect, Wx (x), Wy (y+im->height), Wx (x+im->width), Wy (y));
      GetBackColor (&backcolor);
      GetForeColor (&forecolor);
      BackColor (whiteColor);
      ForeColor (blackColor);
      CopyMask (&((GrafPtr) im->data)->portBits,
                &((GrafPtr) im->mask)->portBits,
                &((GrafPtr) winGraphics)->portBits,
                &srcrect, &srcrect, &dstrect);
      RGBBackColor (&backcolor);
      RGBForeColor (&forecolor);
    EndOffOn
    UnlockPixels (GetGWorldPixMap (im->data));
    UnlockPixels (GetGWorldPixMap (im->mask));
  }else{
    LockPixels (GetGWorldPixMap (im->data));
    BeginOff
      SetRect (&dstrect, Bx (x), By (y+im->height), Bx (x+im->width), By (y));
      GetBackColor (&backcolor);
      GetForeColor (&forecolor);
      BackColor (whiteColor);
      ForeColor (blackColor);
      CopyBits (&((GrafPtr) im->data)->portBits, &((GrafPtr) gworld)->portBits,
                &srcrect, &dstrect, srcCopy, NULL);
      RGBBackColor (&backcolor);
      RGBForeColor (&forecolor);
    On
      SetRect (&dstrect, Wx (x), Wy (y+im->height), Wx (x+im->width), Wy (y));
      GetBackColor (&backcolor);
      GetForeColor (&forecolor);
      BackColor (whiteColor);
      ForeColor (blackColor);
      CopyBits (&((GrafPtr) im->data)->portBits,
                &((GrafPtr) winGraphics)->portBits, &srcrect, &dstrect, srcCopy,
                NULL);
      RGBBackColor (&backcolor);
      RGBForeColor (&forecolor);
    EndOffOn
    UnlockPixels (GetGWorldPixMap (im->data));
  }
  return Val_unit;
}

value gr_create_image (value vw, value vh)
{
  return alloc_image (Long_val (vw), Long_val (vh));
}

value gr_blit_image (value vimage, value vx, value vy)
{
  XY;
  struct grimage *im = (struct grimage *) Bp_val (vimage);
  Rect srcrect, dstrect, worldrect;
  CGrafPtr saveport;
  GDHandle savegdev;

  SetRect (&worldrect, 0, 0, w0, h0);
  SetRect (&srcrect, Bx (x), By (y+im->height), Bx (x+im->width), By (y));
  SectRect (&srcrect, &worldrect, &srcrect);
  dstrect = srcrect;
  OffsetRect (&dstrect, -Bx (x), -By (y+im->height));

  LockPixels (GetGWorldPixMap (im->data));
  LockPixels (GetGWorldPixMap (gworld));
  GetGWorld (&saveport, &savegdev);
  SetGWorld (im->data, NULL);
  BackColor (whiteColor);
  ForeColor (blackColor);
  CopyBits (&((GrafPtr) gworld)->portBits, &((GrafPtr) im->data)->portBits,
            &srcrect, &dstrect, srcCopy, NULL);
  SetGWorld (saveport, savegdev);
  UnlockPixels (GetGWorldPixMap (im->data));
  UnlockPixels (GetGWorldPixMap (gworld));
  return Val_unit;
}

int motion_requested = 0;
short motion_oldx, motion_oldy;
/* local coord versions of motion_oldx, motion_oldy */
static Point lastpt = {SHRT_MAX - 1, SHRT_MAX - 1};

#define Button_down_val 0
#define Button_up_val 1
#define Key_pressed_val 2
#define Mouse_motion_val 3
#define Poll_val 4

value gr_wait_event (value veventlist)
{
  int askmousedown = 0, askmouseup = 0, askkey = 0, askmotion = 0, askpoll = 0;
  GrafPtr saveport;
  value result;
  int mouse_x, mouse_y, button, keypressed, key;
  Point pt;
  int i;

  gr_check_open();
  PushWindowPort (winGraphics);

  while (veventlist != Val_int (0)) {
    switch (Int_val(Field (veventlist, 0))) {
    case Button_down_val:  askmousedown = 1; break;
    case Button_up_val:    askmouseup = 1;   break;
    case Key_pressed_val:  askkey = 1;       break;
    case Mouse_motion_val: askmotion = 1;    break;
    case Poll_val:         askpoll = 1;      break;
    default: Assert (0);
    }
    veventlist = Field (veventlist, 1);
  }

  enter_blocking_section ();

  while (1){
    while (graphQlen > 0 && graphQ[0].when + 300 < TickCount ()){
      DequeueEvent (0);
    }
    for (i = graphQlen - 1; i >= 0; i--){
      int what = graphQ[i].what;
      if (askpoll){
        if (what == keyDown || what == autoKey){
          GetMouse (&pt);
          mouse_x = pt.h;
          mouse_y = pt.v;
          button = Button ();
          keypressed = 1;
          key = graphQ[i].message & charCodeMask;
          goto gotevent;
        }
      }else if (   askmousedown && what == mouseDown
                || askmouseup && what == mouseUp){
        mouse_x = graphQ[i].where.h;
        mouse_y = graphQ[i].where.v;
        button = graphQ[i].what == mouseDown;
        keypressed = 0;
        DequeueEvent (i);
        goto gotevent;
      }else if (askkey && (what == keyDown || what == autoKey)){
        mouse_x = graphQ[i].where.h;
        mouse_y = graphQ[i].where.v;
        button = Button ();
        keypressed = 1;
        key = graphQ[i].message & charCodeMask;
        DequeueEvent (i);
        goto gotevent;
      }
    }
    GetMouse (&pt);
    if (askpoll || askmotion && (pt.h != lastpt.h || pt.v != lastpt.v)){
      mouse_x = pt.h;
      mouse_y = pt.v;
      button = Button ();
      keypressed = 0;
      goto gotevent;
    }
    if (askmotion){
      motion_requested = 1;
      pt = lastpt;
      LocalToGlobal (&pt);
      motion_oldx = pt.h;
      motion_oldy = pt.v;
    }
    GetAndProcessEvents (askmotion ? waitMove : waitEvent,
                         motion_oldx, motion_oldy);
  }

  gotevent:
    PopPort;
    leave_blocking_section ();  /* acquire master lock, handle signals */
    lastpt.h = mouse_x;
    lastpt.v = mouse_y;
    motion_requested = 0;
  
    result = alloc_tuple (5);
    Field (result, 0) = Val_int (Cx (mouse_x));
    Field (result, 1) = Val_int (Cy (mouse_y));
    Field (result, 2) = Val_bool (button);
    Field (result, 3) = Val_bool (keypressed);
    Field (result, 4) = Val_int (key);
    return result;
}

value gr_sound (value vfreq, value vdur)
{
  long freq = Long_val (vfreq);
  long dur = Long_val (vdur);
  long scale;
  Handle h;
  OSErr err;

  if (dur <= 0 || freq <= 0) return Val_unit;
  if (dur > 5000) dur = 5000;
  if (freq > 20000) gr_fail ("sound: frequency is too high", NULL);

  if      (freq > 11025) scale =   2;
  else if (freq >  5513) scale =   4;
  else if (freq >  1378) scale =   8;
  else if (freq >   345) scale =  32;
  else if (freq >    86) scale = 128;
  else                   scale = 512;

  h = GetResource ('snd ', 1000 + scale);
  if (h == NULL){
    gr_fail ("sound: resource error (code = %ld)", (void *) (long) ResError ());
  }
  err = HandToHand (&h);
  if (err != noErr) gr_fail ("sound: out of memory", NULL);
  *(unsigned short *)((*h)+kDurationOffset) = dur * 2;
  Assert (scale * freq < 0x10000);
  *(unsigned short *)((*h)+kSampleRateOffset) = scale * freq;
  HLock (h);
  err = SndPlay (NULL, (SndListHandle) h, false);
  HUnlock (h);
  if (err != noErr){
    gr_fail ("sound: cannot play sound (error code %ld)", (void *) (long) err);
  }

  return Val_unit;
}

value gr_set_window_title (value title)
{
  Str255 ptitle;
  
  strcpy ((char *) ptitle, String_val (title));
  c2pstr ((char *) ptitle);
  SetWTitle (winGraphics, ptitle);
  return Val_unit;
}
