/* pmterm.c -- xterm.c for the OS/2 Presentation Manager
   Copyright (C) 1993, 1994 Eberhard Mattes.

This file is part of GNU Emacs.

GNU Emacs is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.

GNU Emacs is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Emacs; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/process.h>
#include <sys/ioctl.h>

#include "config.h"
#include "lisp.h"
#include "blockinput.h"
#include "keyboard.h"
#include "window.h"
#include "frame.h"
#include "disptab.h"
#include "termhooks.h"
#include "termopts.h"
#include "termchar.h"
#include "dispextern.h"
#include "pmterm.h"
#include "pmemacs.h"

struct pm_queue
{
  struct pm_queue *next;
  pm_event event;
};

static struct pm_queue *pm_queue_first;
static struct pm_queue **pm_queue_add = &pm_queue_first;

int pm_pid = -1;

/* The current serial number for requests which return an answer. */
int pm_serial;

int pm_menu_bar_cookie;
static FRAME_PTR pm_menu_bar_frame;

/* Store here additional data for menu bar events, which does not fit
   into `struct input_event'.  The array index is derived from the
   cookie provided by pmemacs.exe in such a way that a reasonable
   number of events can be handled.  The array index is stored in the
   timestamp member of `struct input_event'.  */

#define PM_MENU_BAR_DATA_SIZE   17
static struct
{
  int number;
  int cookie;
  FRAME_PTR frame;
} pm_menu_bar_data[PM_MENU_BAR_DATA_SIZE];

extern struct face *intern_face (struct frame *f, struct face *face);

void x_make_frame_visible (struct frame *f);
static void dump_rectangle (FRAME_PTR f, int left, int top, int right,
    int bottom);


/* The current code page, defined in emxdep.c. */
extern int cur_code_page;

/* This is the quit character, which is defined in keyboard.c.  It is
   used by the PM_set_terminal_modes function, which is assigned to
   set_terminal_modes_hook. */
extern int quit_char;

/* Dummy variable for simplifying changes to xfaces.c. */
Display *x_current_display;

int pm_session_started = 0;

static int curs_x;
static int curs_y;
static struct frame *updating_frame = 0;

static int outbound_pipe;

static char pm_send_buffer[4096];
static int pm_send_buffered = 0;

/* During an update, nonzero if chars output now should be highlighted.  */
static int highlight;

/* During an update, maximum vpos for ins/del line operations to affect.  */
static int flexlines;

/* Mouse position.  Valid only if mouse_moved is non-zero. */
static int mouse_x;
static int mouse_y;
static FRAME_PTR mouse_frame;

int mouse_face_face_id;
extern int mouse_face_defer;
extern int mouse_face_deferred_gc;
extern FRAME_PTR mouse_face_mouse_frame;
extern int mouse_face_beg_row, mouse_face_beg_col;
extern int mouse_face_end_row, mouse_face_end_col;
extern Lisp_Object mouse_face_window;
extern int mouse_face_mouse_x, mouse_face_mouse_y;


/* Send a message to pmemacs.

   Note: This function should abort more gracefully on failure. */

void pm_send (const void *src, unsigned size)
{
  const char *s;
  int n;

  if (pm_send_buffered > 0)     /* Important! */
    pm_send_flush ();
  s = src;
  while (size != 0)
    {
      n = write (outbound_pipe, s, size);
      if (n == -1 || n == 0)
        fatal ("Cannot send message to PM Emacs.");
      size -= n;
      s += n;
    }
}


/* When sending many small requests, it's better to use this
   function.  We must not use this function if we want to receive data
   from pmemacs. */

void pm_send_collect (const void *src, unsigned size)
{
  if (pm_send_buffered + size > sizeof (pm_send_buffer))
    {
      if (size > sizeof (pm_send_buffer))
        {
          pm_send (src, size);
          return;
        }
      pm_send_flush ();
    }
  memcpy (pm_send_buffer + pm_send_buffered, src, size);
  pm_send_buffered += size;
}


/* Flush buffer filled by pm_send_collect. */

void pm_send_flush (void)
{
  int size;

  if (pm_send_buffered > 0)
    {
      size = pm_send_buffered;
      pm_send_buffered = 0;
      pm_send (pm_send_buffer, size);
    }
}


/* Receive data from pmemacs.
   Note: This should abort more gracefully on failure. */

static int receive (void *dst, size_t size, int return_error)
{
  char *d;
  int n;

  d = dst;
  while (size != 0)
    {
      n = read (0, d, size);
      if (n == -1 || n == 0)
        {
          if (return_error)
            return 0;
          else
            fatal ("Failed to receive data from PM Emacs.");
        }
      size -= n;
      d += n;
    }
  return 1;
}


/* Put an event into the queue, for processing it later. */

static void pm_put_queue (pm_event *event)
{
  struct pm_queue *pmq;
  
  pmq = (struct pm_queue *)xmalloc (sizeof (struct pm_queue));
  pmq->next = NULL;
  pmq->event = *event;
  *pm_queue_add = pmq;
  pm_queue_add = &pmq->next;
  interrupt_input_pending = 1;
}


/* Get an event from the queue. */

static void pm_get_queue (pm_event *event)
{
  struct pm_queue *next;

  *event = pm_queue_first->event;
  next = pm_queue_first->next;
  xfree (pm_queue_first);
  pm_queue_first = next;
  if (pm_queue_first == NULL)
    pm_queue_add = &pm_queue_first;
  else
    interrupt_input_pending = 1;
}


/* Receive an answer from pmemacs.  SERIAL is the serial number of the
   request.  Store the received data to DST.  If DST is NULL, use
   malloc() to allocate a suitable buffer (this is not done when
   receiving a single int).  Store the number of bytes received to
   *SIZE, unless SIZE is NULL.  Process PME_PAINT events if MAY_PAINT
   is true.  Return a pointer to the buffer (DST or allocated).
   Return NULL on failure. */

void *pm_receive (int serial, void *dst, int *size, int may_paint)
{
  pm_event pme;
  struct pm_queue *pmq;
  FRAME_PTR f;

  /* TODO: Check queue for PME_PAINT events if MAY_PAINT is true? */
  for (;;)
    {
      receive (&pme, sizeof (pme), 0);
      switch (pme.header.type)
        {
        case PME_ANSWER:
          if (pme.answer.serial != serial)
            {
              printf ("pm_receive: serial number mismatch: %d vs. %d\r\n",
                      pme.answer.serial, serial);
              if (pme.answer.size > 0)
                {
                  char *tem = (char *)xmalloc (pme.answer.size);
                  receive (tem, pme.answer.size, 0);
                  xfree (tem);
                }
              if (size != NULL)
                *size = 0;
              return (NULL);
            }
          if (pme.answer.size == -1)
            {
              /* DST must be non-NULL. */
              *(int *)dst = pme.answer.one_word;
              if (size != NULL)
                *size = sizeof (int);
            }
          else
            {
              if (dst == NULL)
                dst = xmalloc (pme.answer.size);
              receive (dst, pme.answer.size, 0);
              if (size != NULL)
                *size = pme.answer.size;
            }
          return (dst);

        case PME_PAINT:
          if (may_paint)
            {
              f = (FRAME_PTR)pme.header.frame;
              if (pm_active_frame (f))
                dump_rectangle (f, pme.paint.x0, pme.paint.y0,
                                pme.paint.x1, pme.paint.y1);
            }
          else
            pm_put_queue (&pme);
          break;

        default:
          pm_put_queue (&pme);
          break;
        }
    }
}

/* Keeping a table of active frames.  This is used to avoid acting on
   events for frames already deleted.  After deleting a frame, there
   may still be events for that frame in the queue (pipe 0). */

#define HASH_SIZE 53
#define FRAME_HASH(f) ((unsigned long)f % HASH_SIZE)

struct hash_frame
{
  struct hash_frame *next;
  FRAME_PTR frame;
};

static struct hash_frame *frame_table[HASH_SIZE];

/* Before sending PMR_CREATE, this function should be called to enable
   receiving events from the new frame F. */

void pm_add_frame (FRAME_PTR f)
{
  struct hash_frame *p;
  int hash;

  hash = FRAME_HASH (f);
  for (p = frame_table[hash]; p != 0; p = p->next)
    if (p->frame == f)
      abort ();
  p = (struct hash_frame *) xmalloc (sizeof (*p));
  p->frame = f;
  p->next = frame_table[hash];
  frame_table[hash] = p;
}


/* When deleting a frame, this function should be called to disable
   receiving events from the frame F. */

void pm_del_frame (FRAME_PTR f)
{
  struct hash_frame **p, *q;

  for (p = &frame_table[FRAME_HASH (f)]; *p != 0; p = &(*p)->next)
    if ((*p)->frame == f)
      {
        q = (*p)->next;
        xfree (*p);
        *p = q;
        return;
      }
  abort ();
}


/* Check whether we are allowed to receive events for frame F. */

int pm_active_frame (FRAME_PTR f)
{
  struct hash_frame *p;

  for (p = frame_table[FRAME_HASH (f)]; p != 0; p = p->next)
    if (p->frame == f)
      return 1;
  return 0;
}



/* Turn mouse tracking on or off.  Tell pmemacs to start or stop
   sending PME_MOUSEMOVE events. */

void pm_mouse_tracking (int flag)
{
#if 0
  /* As Emacs 19.23 uses mouse movement events for highlighting
   mouse-sensitive areas, mouse movement events are always enabled and
   pm_mouse_tracking does nothing.  Perhaps later versions of Emacs
   want to turn off mouse movement events, therefore we keep this
   code. */

  pm_request pmr;

  pmr.track.header.type = PMR_TRACKMOUSE;
  pmr.track.header.frame = 0;
  pmr.track.flag = flag;
  pm_send (&pmr, sizeof (pmr));
#endif
}


/* Turn the cursor on or off. */

static void pm_display_cursor (struct frame *f, int on)
{
  pm_request pmr;

  if (!FRAME_VISIBLE_P (f))
    return;
  if (!on && f->phys_cursor_x < 0)
    return;
  if (f != updating_frame)
    {
      curs_x = FRAME_CURSOR_X (f);
      curs_y = FRAME_CURSOR_Y (f);
    }
  if (on && (f->phys_cursor_x != curs_x || f->phys_cursor_y != curs_y))
    {
      pmr.cursor.header.type = PMR_CURSOR;
      pmr.cursor.header.frame = (unsigned long)f;
      pmr.cursor.x = curs_x;
      pmr.cursor.y = curs_y;
      pmr.cursor.on = 1;
      pm_send (&pmr, sizeof (pmr));
      f->phys_cursor_x = curs_x;
      f->phys_cursor_y = curs_y;
    }
  if (!on)
    {
      pmr.cursor.header.type = PMR_CURSOR;
      pmr.cursor.header.frame = (unsigned long)f;
      pmr.cursor.x = 0;
      pmr.cursor.y = 0;
      pmr.cursor.on = 0;
      pm_send (&pmr, sizeof (pmr));
      f->phys_cursor_x = -1;
    }
}


/* TODO: avoid PMR_CLREOL */

void dump_glyphs (struct frame *f, GLYPH *gp, int enable, int used,
                  int x, int y, int cols, int hl)
{
  pm_request pmr, *pmrp;
  int n, clear;
  char *buf;
  int tlen = GLYPH_TABLE_LENGTH;
  Lisp_Object *tbase = GLYPH_TABLE_BASE;

  buf = alloca (sizeof (pmr) + cols);
  pmrp = (pm_request *)buf;
  buf += sizeof (pmr);
  if (enable)
    {
      n = used - x;
      if (n < 0) n = 0;
    }
  else
    n = 0;
  if (n > cols)
    n = cols;
  clear = cols - n;

  while (n > 0)
    {
      /* Get the face-code of the next GLYPH.  */
      int cf, len;
      int g = *gp;
      char *cp;

      GLYPH_FOLLOW_ALIASES (tbase, tlen, g);
      cf = FAST_GLYPH_FACE (g);
      /* Find the run of consecutive glyphs with the same face-code.
	 Extract their character codes into BUF.  */
      cp = buf;
      while (n > 0)
	{
	  g = *gp;
	  GLYPH_FOLLOW_ALIASES (tbase, tlen, g);
	  if (FAST_GLYPH_FACE (g) != cf)
	    break;

	  *cp++ = FAST_GLYPH_CHAR (g);
	  --n;
	  ++gp;
	}

      /* LEN gets the length of the run.  */
      len = cp - buf;

      /* Now output this run of chars, with the font and pixel values
	 determined by the face code CF.  */
      {
	struct face *face = FRAME_DEFAULT_FACE (f);

	/* HL = 3 means use a mouse face previously chosen.  */
	if (hl == 3)
	  cf = mouse_face_face_id;

	if (cf != 0)
	  {
	    /* It's possible for the display table to specify
	       a face code that is out of range.  Use 0 in that case.  */
	    if (cf < 0 || cf >= FRAME_N_COMPUTED_FACES (f))
	      cf = 0;

	    if (cf == 1)
	      face = FRAME_MODE_LINE_FACE (f);
	    else
	      face = intern_face (f, FRAME_COMPUTED_FACES (f) [cf]);
	  }
	else if (hl == 1)
	  {
	    face = FRAME_MODE_LINE_FACE (f);
	  }
        pmrp->glyphs.header.type = PMR_GLYPHS;
        pmrp->glyphs.header.frame = (unsigned long)f;
        pmrp->glyphs.count = len;
        pmrp->glyphs.x = x;
        pmrp->glyphs.y = y;
        pmrp->glyphs.face = face->gc;
        pm_send_collect (pmrp, sizeof (pmr) + len);
      }

      x += len;
    }

  if (clear > 0)
    {
      pmr.clreol.header.type = PMR_CLREOL;
      pmr.clreol.header.frame = (unsigned long)f;
      pmr.clreol.y = y;
      pmr.clreol.x0 = x;
      pmr.clreol.x1 = x + clear; /* -1 ? */
      pm_send_collect (&pmr, sizeof (pmr));
    }
  pm_send_flush ();
}


static void dump_rectangle (FRAME_PTR f, int left, int top,
                            int right, int bottom)
{
  struct frame_glyphs *active_frame = FRAME_CURRENT_GLYPHS (f);
  int cols, rows, y, cursor_flag;

  if (FRAME_GARBAGED_P (f))
    return;

  /* Clip the rectangle to what can be visible.  */
  if (left < 0)
    left = 0;
  if (top < 0)
    top = 0;
  if (right >= f->width)
    right = f->width - 1;
  if (bottom >= f->height)
    bottom = f->height - 1;

  /* Get size in chars of the rectangle.  */
  cols = 1 + right - left;
  rows = 1 + bottom - top;

  /* If rectangle has zero area, return.  */
  if (rows <= 0) return;
  if (cols <= 0) return;

  cursor_flag = (f->phys_cursor_x >= 0);
  pm_display_cursor (f, 0);

  /* Display the text in the rectangle, one text line at a time.  */

  for (y = top; y <= bottom; y++)
    dump_glyphs (f, &active_frame->glyphs[y][left],
                 active_frame->enable[y], active_frame->used[y],
                 left, y, cols, active_frame->highlight[y]);
  if (cursor_flag)
    pm_display_cursor (f, 1);
}


/* Send an `empty' PMR_MENU request to satisfy an unhandled
   PME_MENUBAR event.  */

void pm_default_menu (void)
{
  pm_request pmr;

  if (pm_menu_bar_cookie != 0)
    {
      pmr.menu.header.type = PMR_MENU;
      pmr.menu.header.frame = (unsigned long)pm_menu_bar_frame;
      pmr.menu.serial = pm_serial++;
      pmr.menu.count = 0;
      pmr.menu.size = 0;
      pmr.menu.cookie = pm_menu_bar_cookie;
      pm_send (&pmr, sizeof (pmr));
      pm_menu_bar_cookie = 0;
    }
}


Lisp_Object
map_event_to_object (event, f)
     struct input_event *event;
     FRAME_PTR f;
{
  Lisp_Object items, key, string;
  int i, idx;

  pm_menu_bar_cookie = 0;
  pm_menu_bar_frame = pm_menu_bar_data[event->timestamp].frame;
  if (!pm_active_frame (pm_menu_bar_frame))
    return Qnil;

  idx = 3 * pm_menu_bar_data[event->timestamp].number;

  items = FRAME_MENU_BAR_ITEMS (f);
  key = Qnil;
  if (XTYPE (items) == Lisp_Vector && idx + 2 <= XVECTOR (items)->size)
    {
      for (i = 0; i <= idx; i += 3)
        {
          string = XVECTOR (items)->contents[i + 1];
          if (NILP (string))
            break;
        }
      if (i > idx)
        key = XVECTOR (items)->contents[idx + 0];
    }
  if (!NILP (key))
    pm_menu_bar_cookie = pm_menu_bar_data[event->timestamp].cookie;
  return key;
}


/* Given a pixel position (PIX_X, PIX_Y) on the frame F, return
   glyph co-ordinates in (*X, *Y).  Set *BOUNDS to the rectangle
   that the glyph at X, Y occupies, if BOUNDS != 0.
   If NOCLIP is nonzero, do not force the value into range.  */

void
pixel_to_glyph_coords (f, pix_x, pix_y, x, y, bounds, noclip)
     FRAME_PTR f;
     register int pix_x, pix_y;
     register int *x, *y;
     XRectangle *bounds;
     int noclip;
{
  /*TODO*/
  *x = pix_x;
  *y = pix_y;
  if (bounds)
    {
      bounds->x = pix_x;
      bounds->y = pix_y;
      bounds->width = 1;
      bounds->height = 1;
    }
}

void
glyph_to_pixel_coords (f, x, y, pix_x, pix_y)
     FRAME_PTR f;
     register int x, y;
     register int *pix_x, *pix_y;
{
  /*TODO*/
  *pix_x = x;
  *pix_y = y;
}


/* This function can be called at quite inconvenient times; therefore
   it must not do anything which may result in a call to `message2' or
   `update_frame'.  */

int PM_read_socket (int sd, struct input_event *bufp, int numchars,
                    int waitp, int expected)
{
  int count = 0;
  pm_event pme;
  int nread;
  Lisp_Object frame;
  FRAME_PTR f;

  if (interrupt_input_blocked)
    {
      interrupt_input_pending = 1;
      return -1;
    }
  interrupt_input_pending = 0;
  if (numchars <= 0)
    abort ();

  if (pm_queue_first != NULL)
    pm_get_queue (&pme);
  else
    {
      if (ioctl (0, FIONREAD, &nread) < 0 || nread < sizeof (pme))
        return 0;
      receive (&pme, sizeof (pme), 0);
    }
  f = (FRAME_PTR)pme.header.frame;
  if (!pm_active_frame (f))
    return 0;
  switch (pme.header.type)
    {
    case PME_PAINT:

      /* A frame needs repainting. */

      dump_rectangle (f, pme.paint.x0, pme.paint.y0,
                      pme.paint.x1, pme.paint.y1);
      break;

    case PME_KEY:

      /* A key has been pressed. */

      switch (pme.key.type)
        {
        case PMK_ASCII:
          bufp->kind = ascii_keystroke;
          bufp->code = pme.key.code;
          break;
        case PMK_VIRTUAL:
          bufp->kind = non_ascii_keystroke;
          bufp->code = pme.key.code | (1 << 28);
          break;
        default:
          abort ();
        }
      bufp->modifiers = pme.key.modifiers;
      bufp->timestamp = 0;
      XSET (bufp->frame_or_window, Lisp_Frame, f);
      ++count; ++bufp;
      break;

    case PME_BUTTON:

      /* A mouse button has been pressed or released. */

      bufp->kind = mouse_click;
      bufp->code = pme.button.button;
      XSET (bufp->frame_or_window, Lisp_Frame, f);
      XFASTINT (bufp->x) = pme.button.x;
      XFASTINT (bufp->y) = pme.button.y;
      bufp->modifiers = pme.button.modifiers;
      bufp->timestamp = pme.button.timestamp;
      ++count; ++bufp;
      break;

    case PME_SIZE:

      /* The size of the frame has been changed by the user.  Adjust
         frame and set new size. */

      if (pme.size.width != FRAME_WIDTH (f)
          || pme.size.height != FRAME_HEIGHT (f))
        {
          change_frame_size (f, pme.size.height, pme.size.width, 0, 1);
          f->display.x->pixel_height = pme.size.pix_height;
          f->display.x->pixel_width = pme.size.pix_width;
          SET_FRAME_GARBAGED (f);
        }
      break;

    case PME_RESTORE:

      /* A frame window is being restored. */

      if (f->async_visible == 0)
        {
          f->async_visible = 1;
          f->async_iconified = 0;
          SET_FRAME_GARBAGED (f);
        }
      break;

    case PME_MINIMIZE:

      /* A frame window is being minimized. */

      f->async_visible = 0;
      f->async_iconified = 1;
      break;

    case PME_MENUBAR:

      /* Update a menu bar entry. */

      bufp->kind = mouse_click;
      bufp->code = 0;           /* first button */
      XSET (bufp->frame_or_window, Lisp_Frame, f);
      XFASTINT (bufp->x) = pme.menubar.x;
      XFASTINT (bufp->y) = -1;  /* menu bar */
      bufp->modifiers = 0;

      /* This assumes that cookies are incremented by a number (such
         as 1, which is in fact used by pmemacs.exe) which is not a
         multiple of PM_MENU_BAR_DATA_SIZE.  */

      bufp->timestamp = pme.menubar.cookie % PM_MENU_BAR_DATA_SIZE;
      pm_menu_bar_data[bufp->timestamp].number = pme.menubar.number;
      pm_menu_bar_data[bufp->timestamp].cookie = pme.menubar.cookie;
      pm_menu_bar_data[bufp->timestamp].frame = f;
      ++count; ++bufp;
      break;

    case PME_MOUSEMOVE:

      /* The mouse has been moved. */

      mouse_frame = (FRAME_PTR)pme.mouse.header.frame;
      mouse_x = pme.mouse.x;
      mouse_y = pme.mouse.y;
      mouse_moved = 1;
      note_mouse_highlight (mouse_frame, mouse_x, mouse_y);
      break;

    case PME_FRAMEMOVE:

      /* A frame has been moved. */

      f->display.x->left_pos = pme.framemove.left;
      f->display.x->top_pos = pme.framemove.top;
      break;

    case PME_ANSWER:

      /* A synchronous answer.  Ignore it. */

      break;

    default:
      fatal ("Bad event type %d from PM Emacs.", pme.header.type);
    }
  return count;
}


static PM_set_terminal_modes (void)
{
  pm_request pmr;

  pmr.quitchar.header.type = PMR_QUITCHAR;
  pmr.quitchar.header.frame = 0;
  pmr.quitchar.quitchar = quit_char;
  pm_send (&pmr, sizeof (pmr));
}


static int PM_cursor_to (int row, int col)
{
  curs_x = col;
  curs_y = row;
  if (updating_frame == 0)
    pm_display_cursor (selected_frame, 1);
}


static PM_write_glyphs (GLYPH *start, int len)
{
  pm_request pmr;
  struct frame *f;

  f = updating_frame;
  if (f == 0)
    {
      f = selected_frame;
      curs_x = f->cursor_x;
      curs_y = f->cursor_y;
    }
  dump_glyphs (f, start, 1, curs_x + len, curs_x, curs_y, len, highlight);
  if (updating_frame == 0)
    {
      f->cursor_x += len;
      pm_display_cursor (f, 1);
      f->cursor_x -= len;
    }
  else
    curs_x += len;
}


static PM_set_terminal_window (int n)
{
  if (updating_frame == 0)
    abort ();
  if (n <= 0 || n > updating_frame->height)
    flexlines = updating_frame->height;
  else
    flexlines = n;
}


static PM_ins_del_lines (int vpos, int n)
{
  pm_request pmr;

  if (updating_frame == 0)
    abort ();
  if (vpos >= flexlines)
    return;
  pmr.lines.header.type = PMR_LINES;
  pmr.lines.header.frame = (unsigned long)updating_frame;
  pmr.lines.y = vpos;
  pmr.lines.max_y = flexlines;
  pmr.lines.count = n;
  pm_send (&pmr, sizeof (pmr));
}


static PM_update_begin (FRAME_PTR f)
{
  if (f == 0)
    abort ();
  updating_frame = f;
  flexlines = f->height;
  highlight = 0;
  pm_display_cursor (f, 0);

  /* Borrowed from xterm.c. */

  if (f == mouse_face_mouse_frame)
    {
      /* Don't do highlighting for mouse motion during the update.  */
      mouse_face_defer = 1;
      if (!NILP (mouse_face_window))
	{
	  int firstline, lastline, i;
	  struct window *w = XWINDOW (mouse_face_window);

	  /* Find the first, and the last+1, lines affected by redisplay.  */
	  for (firstline = 0; firstline < f->height; firstline++)
	    if (FRAME_DESIRED_GLYPHS (f)->enable[firstline])
	      break;

	  lastline = f->height;
	  for (i = f->height - 1; i >= 0; i--)
	    {
	      if (FRAME_DESIRED_GLYPHS (f)->enable[i])
		break;
	      else
		lastline = i;
	    }

	  /* Can we tell that this update does not affect the window
	     where the mouse highlight is?  If so, no need to turn off.  */
	  if (! (firstline > (XFASTINT (w->top) + window_internal_height (w))
		 || lastline < XFASTINT (w->top)))
	    /* Otherwise turn off the mouse highlight now.  */
	    clear_mouse_face ();
	}
    }
}


static PM_update_end (struct frame *f)
{
  pm_display_cursor (f, 1);
  if (f == mouse_face_mouse_frame)
    mouse_face_defer = 0;
  updating_frame = 0;
}


static PM_clear_frame ()
{
  pm_request pmr;
  struct frame *f;

  f = updating_frame;
  if (f == 0)
    f = selected_frame;
  f->phys_cursor_x = -1;
  curs_x = 0;
  curs_y = 0;
  pmr.header.type = PMR_CLEAR;
  pmr.header.frame = (unsigned long)f;
  pm_send (&pmr, sizeof (pmr));
}

static PM_clear_end_of_line (int first_unused)
{
  pm_request pmr;
  struct frame *f = updating_frame;

  if (f == 0)
    abort ();

  if (curs_y < 0 || curs_y >= f->height)
    return;
  if (first_unused <= 0)
    return;
  if (first_unused >= f->width)
    first_unused = f->width;

  pmr.clreol.header.type = PMR_CLREOL;
  pmr.clreol.header.frame = (unsigned long)f;
  pmr.clreol.y = curs_y;
  pmr.clreol.x0 = curs_x;
  pmr.clreol.x1 = first_unused;
  pm_send (&pmr, sizeof (pmr));
}


/* This is called after a redisplay on frame F.  */

static
PM_frame_up_to_date (FRAME_PTR f)
{
  if (mouse_face_deferred_gc || f == mouse_face_mouse_frame)
    {
      note_mouse_highlight (mouse_face_mouse_frame,
			    mouse_face_mouse_x, mouse_face_mouse_y);
      mouse_face_deferred_gc = 0;
    }
}


static PM_reassert_line_highlight (int new, int vpos)
{
  highlight = new;
}


static PM_change_line_highlight (int new_highlight, int vpos,
                                 int first_unused_hpos)
{
  highlight = new_highlight;
  PM_cursor_to (vpos, 0);
  PM_clear_end_of_line (updating_frame->width);
}


/* Let pmemacs ring the bell. */

PM_ring_bell ()
{
  pm_request pmr;

  pmr.bell.header.type = PMR_BELL;
  pmr.bell.header.frame = (unsigned long)selected_frame;
  pmr.bell.visible = visible_bell;
  pm_send (&pmr, sizeof (pmr));
}


/* Return the current mouse position. */

static void PM_mouse_position (FRAME_PTR *f, Lisp_Object *bar_window,
                               enum scroll_bar_part *part,
                               Lisp_Object *x, Lisp_Object *y,
                               unsigned long *time)
{
  if (mouse_moved)
    {
      *f = mouse_frame;
      XSET (*x, Lisp_Int, mouse_x);
      XSET (*y, Lisp_Int, mouse_y);
      *bar_window = Qnil;
      *time = 0;
      mouse_moved = 0;
    }
  else
    {
      /* Probably we don't need this -- is PM_mouse_position ever
         called when mouse_moved is zero? */
      pmd_mousepos result;
      pm_request pmr;

      pmr.mousepos.header.type = PMR_MOUSEPOS;
      pmr.mousepos.header.frame = -1;
      pmr.mousepos.serial = pm_serial++;
      pm_send (&pmr, sizeof (pmr));
      if (pm_receive (pmr.mousepos.serial, &result, NULL, 0) != NULL
          && result.frame != 0)
        {
          *f = (FRAME_PTR)result.frame;
          XSET (*x, Lisp_Int, result.x);
          XSET (*y, Lisp_Int, result.y);
          *bar_window = Qnil;
          *time = 0;
        }
    }
}


void x_destroy_window (struct frame *f)
{
  pm_request pmr;

  if (f == mouse_face_mouse_frame)
    {
      mouse_face_beg_row = mouse_face_beg_col = -1;
      mouse_face_end_row = mouse_face_end_col = -1;
      mouse_face_window = Qnil;
    }

  pm_del_frame (f);
  pmr.header.type = PMR_DESTROY;
  pmr.header.frame = (unsigned long)f;
  pm_send (&pmr, sizeof (pmr));
  free_frame_faces (f);
  free_frame_menubar (f);
  free_pm_menu_bar (f->pm_menu_bar_items);
  f->pm_menu_bar_items = 0;
}


void x_set_mouse_position (struct frame *f, int x, int y)
{
  /*TODO*/
}


/* Move the mouse to position pixel PIX_X, PIX_Y relative to frame F.  */

void x_set_mouse_pixel_position (struct frame *f, int pix_x, int pix_y)
{
  /*TODO*/
}


static void PM_frame_raise_lower (struct frame *f, int raise)
{
  pm_request pmr;

  pmr.header.type = (raise ? PMR_RAISE : PMR_LOWER);
  pmr.header.frame = (unsigned long)f;
  pm_send (&pmr, sizeof (pmr));
}


void x_make_frame_visible (struct frame *f)
{
  pm_request pmr;

  if (!FRAME_VISIBLE_P (f))
    {
      pmr.visible.header.type = PMR_VISIBLE;
      pmr.visible.header.frame = (unsigned long)f;
      pmr.visible.visible = 1;
      pm_send (&pmr, sizeof (pmr));
      f->async_visible = 1;
      f->async_iconified = 0;
    }
}


void x_make_frame_invisible (struct frame *f)
{
  pm_request pmr;

  if (!f->async_visible)
    return;
  f->async_visible = 0;
  pmr.visible.header.type = PMR_VISIBLE;
  pmr.visible.header.frame = (unsigned long)f;
  pmr.visible.visible = 0;
  pm_send (&pmr, sizeof (pmr));
}


void x_iconify_frame (struct frame *f)
{
  pm_request pmr;

  f->async_visible = 0;
  f->async_iconified = 1;
  pmr.header.type = PMR_ICONIFY;
  pmr.header.frame = (unsigned long)f;
  pm_send (&pmr, sizeof (pmr));
}


void x_set_window_size (struct frame *f, int change_gravity,
                        int cols, int rows)
{
  pm_request pmr;

  check_frame_size (f, &rows, &cols);
  change_frame_size (f, rows, cols, 0, 0);
  pmr.size.header.type = PMR_SIZE;
  pmr.size.header.frame = (unsigned long)f;
  pmr.size.width = cols;
  pmr.size.height = rows;
  pm_send (&pmr, sizeof (pmr));

  /* If cursor was outside the new size, mark it as off.  */
  if (f->phys_cursor_y >= rows
      || f->phys_cursor_x >= cols)
    {
      f->phys_cursor_x = -1;
      f->phys_cursor_y = -1;
    }
  SET_FRAME_GARBAGED (f);
}


void x_set_offset (struct frame *f, int xoff, int yoff, int change_gravity)
{
  pm_request pmr;

  pmr.setpos.header.type = PMR_SETPOS;
  pmr.setpos.header.frame = (unsigned long)f;
  pmr.setpos.left = xoff;
  pmr.setpos.top = yoff;
  pm_send (&pmr, sizeof (pmr));
}


void x_focus_on_frame (struct frame *f)
{
  pm_request pmr;

  pmr.header.type = PMR_FOCUS;
  pmr.header.frame = (unsigned long)f;
  pm_send (&pmr, sizeof (pmr));
}


void pm_init (void)
{
  int i, pipe_in[2], pipe_out[2];
  char arg1[12], arg2[12], arg3[12];
  pm_request pmr;
  pmd_config answer;

  for (i = 0; i < 64; i++)
    fcntl (i, F_SETFD, 1);

  sprintf (arg1, "%d", getpid ());

  if (pipe (pipe_out) != 0)
    fatal ("Cannot open pipe for PM Emacs.");
  fcntl (pipe_out[1], F_SETFD, 1); /* Don't pass write end to child */
  outbound_pipe = pipe_out[1];
  setmode (outbound_pipe, O_BINARY);
  sprintf (arg2, "%d", pipe_out[0]);

  if (pipe (pipe_in) != 0)
    fatal ("Cannot open pipe for PM Emacs.");
  dup2 (pipe_in[0], 0);         /* Replace stdin with pipe */
  close (pipe_in[0]);
  fcntl (0, F_SETFD, 1);        /* Don't pass read end to child */
  setmode (0, O_BINARY);
  sprintf (arg3, "%d", pipe_in[1]);

  pm_pid = spawnlp (P_PM, "pmemacs.exe", "pmemacs.exe",
                    arg1, arg2, arg3, 0);
  if (pm_pid == -1)
    fatal ("Cannot start PM Emacs.");

  close (pipe_out[0]);
  close (pipe_in[1]);
  pm_session_started = 1;

  pmr.initialize.header.type = PMR_INITIALIZE;
  pmr.initialize.header.frame = 0;
  pmr.initialize.codepage = cur_code_page != 0 ? cur_code_page : 850;
  pm_send (&pmr, sizeof (pmr));

  clear_frame_hook = PM_clear_frame;
  clear_end_of_line_hook = PM_clear_end_of_line;
  ins_del_lines_hook = PM_ins_del_lines;
  set_terminal_window_hook = PM_set_terminal_window;
#if 0
  insert_glyphs_hook = PM_insert_glyphs;
  delete_glyphs_hook = PM_delete_glyphs;
#endif
  frame_raise_lower_hook = PM_frame_raise_lower;
  change_line_highlight_hook = PM_change_line_highlight;
  reassert_line_highlight_hook = PM_reassert_line_highlight;
  mouse_position_hook = PM_mouse_position;
  write_glyphs_hook = PM_write_glyphs;
  ring_bell_hook = PM_ring_bell;
  set_terminal_modes_hook = PM_set_terminal_modes;
  update_begin_hook = PM_update_begin;
  update_end_hook = PM_update_end;
  read_socket_hook = PM_read_socket;
  cursor_to_hook = PM_cursor_to;
  frame_up_to_date_hook = PM_frame_up_to_date;

  scroll_region_ok = 0;		/* we won't scroll partial frames */
  char_ins_del_ok = 0;		/* just as fast to write the line */
  line_ins_del_ok = 1;		/* use GpiBitBlt */
  fast_clear_end_of_line = 1;	/* PM does this well */
  memory_below_frame = 0;	/* we don't remember what scrolls 
				   off the bottom */
  baud_rate = 19200;

  /* Get the number of planes. */
  pmr.config.header.type = PMR_CONFIG;
  pmr.config.header.frame = 0;
  pmr.config.serial = pm_serial++;
  pm_send (&pmr, sizeof (pmr));
  if (pm_receive (pmr.config.serial, &answer, NULL, 0) == NULL)
    x_screen_planes = 1;
  else
    x_screen_planes = answer.planes;
}


void pm_exit (void)
{
  pm_request pmr;
  pm_event pme;

  pmr.header.type = PMR_CLOSE;
  pmr.header.frame = 0;
  pm_send (&pmr, sizeof (pmr));

  /* Wait until the pipe is closed, that is, until pmemacs.exe is
     dead.  Why use waitpid() if we can do without? :-) */

  while (receive (&pme, sizeof (pme), 1))
    if (pme.header.type == PME_ANSWER && pme.answer.size > 0)
      {
        char *tem = (char *)xmalloc (pme.answer.size);
        if (!receive (tem, pme.answer.size, 1))
          break;
        xfree (tem);
      }

  pm_session_started = 0;
  close (outbound_pipe);
  close (0);
}


syms_of_xterm ()
{
  mouse_face_window = Qnil;
  staticpro (&mouse_face_window);
}


void x_handle_selection_request (struct input_event *event)
{
}

void x_handle_selection_clear (struct input_event *event)
{
}


static XFontStruct *pm_fonts = 0;


XFontStruct *XLoadQueryFont (Display *dpy, char *name)
{
  XFontStruct *p;

  for (p = pm_fonts; p != 0; p = p->next)
    if (strcmp (p->name, name) == 0)
      return p;
  p = (XFontStruct *)xmalloc (sizeof (*p));
  p->max_bounds.width = 1;      /* TODO */
  p->ascent = 1;                /* TODO */
  p->descent = 0;               /* TODO */
  _strncpy (p->name, name, sizeof (p->name));
  p->next = pm_fonts;
  pm_fonts = p;
  return p;
}


int XParseColor (Display *dpy, Colormap cmap, char *name, XColor *color)
{
  return defined_color (name, &color->pixel);
}
