/*
 * Copyright (C) 2000 Laurent Mazet <mazet@crm.mot.com>
 *
 * This program is free software and may be used for any purpose.  This
 * copyright notice must be maintained.  Paul Kienzle is not responsible
 * for the consequences of using this software.
 */

/*
 * Graphics extention for hacking gnuplot on X11
 */

#include <string>

#include <octave/oct.h>
#include <octave/toplev.h>
#include <octave/parse.h>

extern "C" {
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
}

#include "graphics.h"
using namespace std;

/*
 * Ask gnuplot for current figure
 */

string find_gnuplot_window (const char* const func) 
{
  /* send gget command to get terminal */
  octave_value_list gget_args;
  gget_args(0) = "terminal";
  octave_value_list gget_ret = feval ("gget", gget_args, 0);

  /* get figure number */
  string st = gget_ret(0).string_value();
  int fig_nb = -1;
  if (st.length() > 2 && (st[0] == 'x' || st[0] == 'X') &&
      st[1] == '1' && st[2] == '1')
    fig_nb = strtol (st.substr(3, st.length()).c_str(), NULL, 10);

  /* check if there is a gnuplot figure */
  if (fig_nb < 0) {
    warning("%s: no figure.", func);
    return ("");
  }

  /* Identify current figure window title */
  char win_name[11]; /* Big enough to hold "Gnuplot " + 2 digit number */
  if (fig_nb > 0)
    sprintf(win_name, "Gnuplot %2d", fig_nb);
  else
    strcpy(win_name, "Gnuplot");

  return (win_name);
}

/*
 * Recursively search the window heirarchy for a window of the given name
 */

Window find_x11_window(Display *display, Window top, std::string name)
{
  Window root, parent, *children, found;
  char *window_name;
  unsigned int N, i;

  /* Check if we've already got it */
  XFetchName(display, top, &window_name);
  if (!window_name==None && name.compare(window_name) == 0) {
    XFree(window_name);
    return top;
  }

  /* If not, recursively check all children */
  if (!XQueryTree(display, top, &root, &parent, &children, &N)) {
    XFree(window_name);
    return 0;
  }
  
  found = 0;
  for (i=0; i < N; i++) {
    found = find_x11_window(display, children[i], name);
    if (found) break;
  }
  XFree(children);
  XFree(window_name);
  return found;
}

/*
 * Initialize gwindow structure
 */

int init_gwindow (gwindow &gw, string wname, const char* const func)
{
  
  /* Find plot window from title */
  gw.display = XOpenDisplay(NULL);
  gw.screen = DefaultScreen(gw.display);
  gw.root = RootWindow(gw.display, gw.screen);
  gw.window = find_x11_window(gw.display, gw.root, wname);
  if (gw.window == 0) {
    warning("%s: plot window not found.", func);
    XCloseDisplay (gw.display);
    return 0;
  }

  /* Make sure we can capture the clicks */
  XWindowAttributes wattr;
  XGetWindowAttributes(gw.display, gw.window, &wattr);
  if (wattr.all_event_masks & ButtonPressMask) {
    warning ("%s: could not capture button events.", func);
    XCloseDisplay (gw.display);
    return 0;
  }
  
  /* Set cross-hairs cursor */
  Cursor cursor;
  cursor = XCreateFontCursor (gw.display, XC_crosshair);
  XDefineCursor (gw.display, gw.window, cursor);

  /* Get width and height */
  Window wroot;
  int wx, wy;
  unsigned int bw;
  XGetGeometry(gw.display, gw.window,
	       &wroot, &wx, &wy, &gw.width, &gw.height, &bw, &gw.depth);

  /* Check scale and origin */
  octave_value_list gget_args, gget_ret;
  string st;
  char *second;

  gget_args(0) = "size";
  gget_ret = feval ("gget", gget_args, 0);
  st = gget_ret(0).string_value();
  gw.xscale = strtod (st.substr (st.rfind(" "), st.length()).c_str(), &second);
  gw.yscale = strtod (second+1, NULL);

  gget_args(0) = "origin";
  gget_ret = feval ("gget", gget_args, 0);
  st = gget_ret(0).string_value();
  gw.xorigin = strtod (st.c_str(), &second);
  gw.yorigin = strtod (second+1, NULL);

  /* now grab the keyboard on window to avoid gnuplot event */
//   XGrabKeyboard (gw.display, gw.window,
//                  False, GrabModeSync, GrabModeSync, CurrentTime);

  return 1;
}

/*
 * close gwindow structure
 */

void close_gwindow (gwindow &gw)
{
//   XUngrabKeyboard (gw.display, CurrentTime);

  /* Stop watching for key/button press (among others) */
  XSelectInput (gw.display, gw.window, 0);

  /* Restore cursor */
  XUndefineCursor (gw.display, gw.window);
   
  XCloseDisplay (gw.display);
}

/*
 * warp on window center
 */

void warp_center (gwindow &gw)
{
  
  /* Warp to window center */
  XWarpPointer (gw.display, gw.root, gw.window, 0, 0,
		0, 0,
		int(gw.width*(gw.xorigin+gw.xscale/2)),
		int(gw.height*(1-gw.yorigin-gw.yscale/2)));

}

/*
 * Get one point or abort if any key. Return button number or 0 if abort.
 */

int get_point (gwindow &gw, int &x, int &y)
{

  /* Watch for key/button press (among others) */
  XSelectInput (gw.display, gw.window, ButtonPressMask|KeyPressMask);

  XEvent event;
  XNextEvent (gw.display, &event);
  if (event.type != ButtonPress)
    return 0;

  x = event.xbutton.x;
  y = event.xbutton.y;
  return event.xbutton.button;
}

/*
 * Get one area, one point or abort if any key. 
 * Return button number, 0  for a key, or -1 if abort.
 */

int get_area_point (gwindow &gw, MArray<int> &x, MArray<int> &y)
{

  /* Watch for key/button press (among others) */
  XSelectInput (gw.display, gw.window, ButtonPressMask|KeyPressMask);
  
  /* First point */
  XEvent event;
  XNextEvent (gw.display, &event);
  if (event.type != ButtonPress)
    return 0;

  if (x.length() != 2)
    x.resize(2);
  if (y.length() != 2)
    y.resize(2);
  
  x(0) = event.xbutton.x;
  y(0) = event.xbutton.y;
  if (event.xbutton.button != 1)
    return event.xbutton.button;

  XGCValues gcv;
  GC gc;
  
  // FIX ME! White is 0xffffff for TrueColor !
  gcv.function = GXxor;
  gcv.foreground = 0xffffff;
  gc = XCreateGC (gw.display, gw.window, GCForeground|GCFunction, &gcv);
  
  x(1) = x(0);
  y(1) = x(0);      
  
  XSelectInput (gw.display, gw.window,
		ButtonPressMask | PointerMotionMask | KeyPressMask);
  /* Loop to get second point */
  while (1) {
    XNextEvent (gw.display, &event);
    
    /* Erase old rectangle */
    if (x(1) != x(0) || y(1) != x(0))
      XDrawRectangle (gw.display, gw.window, gc,
		      (x(1) < x(0)) ? x(1) : x(0),
		      (y(1) < y(0)) ? y(1) : y(0),
		      abs (x(1) - x(0))+1,
		      abs (y(1) - y(0))+1);
   
    switch (event.type) {
    case ButtonPress:
      
      XFreeGC(gw.display, gc);
      
      /* Check button and quit */
      x(1) = event.xbutton.x;
      y(1) = event.xbutton.y;
      
      XFlush(gw.display);
      return (event.xbutton.button == 1) ? 1 : -1;
      break;
      
    case MotionNotify:
    
      /* Draw new rectangle */
      x(1) = event.xmotion.x;
      y(1) = event.xmotion.y;
      if (x(1) != x(0) || y(1) != x(0))
	XDrawRectangle (gw.display, gw.window, gc,
			(x(1) < x(0)) ? x(1) : x(0),
			(y(1) < y(0)) ? y(1) : y(0),
			abs (x(1) - x(0))+1,
			abs (y(1) - y(0))+1);
      break;

    case KeyPress:

      XFreeGC(gw.display, gc);

      /* Quit and send key code */
      XFlush(gw.display);
      return 0;
      break;
    }
  }

  XFreeGC(gw.display, gc);

  XFlush(gw.display);
  return -1;
}


/*
 * Guess border from gnuplot image
 */

ColumnVector guess_border (gwindow &gw)
{

  // FIX ME! White is 0xffffffL for TrueColor !
  XImage *image = XGetImage (gw.display, gw.window,
			     int(gw.width*gw.xorigin),
			     int(gw.height*(1-gw.yorigin-gw.yscale)),
			     int(gw.width*gw.xscale),
			     int(gw.height*gw.yscale),
			     0xffffffL, ZPixmap);  
  int width = image->width;
  int height = image->height;
  
  /* compute line and column correlations */
  MArray<int> x_cor (width, 0);
  MArray<int> y_cor (height, 0);

  for (int i=0; i<width; i++)
    for (int j=0; j<height; j++)
      if (XGetPixel (image, i, j)) {
	x_cor (i) ++;
	y_cor (j) ++;
      }

  /* find two x minima */
  int x1=0;
  for (int i=1; i<width; i++)
    if (x_cor(i) < x_cor(x1))
      x1 = i;
  int x2 = (x1 == 0) ? 1 : 0;
  for (int i=0; i<width; i++)
    if (x_cor(i) < x_cor(x2) && i != x1 && i != x1-1 && i != x1+1)
      x2 = i;

  /* find two y minima */
  int y1=0;
  for (int j=0; j<height; j++)
    if (y_cor(j) < y_cor(y1))
      y1 = j;
  int y2 = (y1 == 0) ? 1 : 0;
  for (int j=0; j<height; j++)
    if (y_cor(j) < y_cor(y2) && j != y1 && j != y1-1 && j != y1+1)
      y2 = j;
  
  /* take multiplot into account */
  x1 += int(gw.width*gw.xorigin);
  x2 += int(gw.width*gw.xorigin);
  y1 += int(gw.height*(1-gw.yorigin-gw.yscale));
  y2 += int(gw.height*(1-gw.yorigin-gw.yscale));

  /* create axis vector */
  ColumnVector axis (4);

  axis(0) = (x1 < x2) ? x1 : x2;
  axis(1) = (x1 < x2) ? x2 : x1;
  axis(2) = (y1 < y2) ? y1 : y2;
  axis(3) = (y1 < y2) ? y2 : y1;
  
  // Freed image
  XDestroyImage (image);


  return axis;
}

/*
 * Guess axis
 */

ColumnVector guess_axis (const char* const func)
{
  ColumnVector axis(4);
  octave_value_list gget_args, gget_ret;
  string st;

  /* Get xrange */
  gget_args(0) = "xrange";
  gget_ret = feval ("gget", gget_args, 0);
  st = gget_ret(0).string_value();

  /* Check if axis are set or if nowriteback option is not active */
  if (st.find("[ * : * ]") == 0 && st.find("nowriteback") < st.length()) {
    warning("%s: no axis set and `nowriteback' option active.", func);
    return (ColumnVector (0));
  }

  if (st.find("[ * : * ]") == 0) {
    /* Writeback option is active */
    axis(0) =  strtod (st.substr(st.rfind("[")+1, st.length()).c_str(), NULL);
    axis(1) =  strtod (st.substr(st.rfind(":")+1, st.length()).c_str(), NULL);
  }
  else {
    /* Axis are set */
    axis(0) = strtod (st.substr(1, st.find(":")).c_str(), NULL);
    axis(1) = strtod (st.substr(st.find(":")+1, st.find("]")).c_str(), NULL);
  }

  /* Get yrange */
  gget_args(0) = "yrange";
  gget_ret = feval ("gget", gget_args, 0);
  st = gget_ret(0).string_value();

  /* Check if axis are set or if nowriteback option is not active */
  if (st.find("[ * : * ]") == 0 && st.find("nowriteback") < st.length()) {
    warning("%s: no axis set and `nowriteback' option active.",func);
    return (ColumnVector (0));
  }

  if (st.find("[ * : * ]") == 0) {
    /* Writeback option is active */
    axis(2) =  strtod (st.substr(st.rfind("[")+1, st.length()).c_str(), NULL);
    axis(3) =  strtod (st.substr(st.rfind(":")+1, st.length()).c_str(), NULL);
  }
  else {    /* Axis are set */
    axis(2) = strtod (st.substr(1, st.find(":")).c_str(), NULL);
    axis(3) = strtod (st.substr(st.find(":")+1, st.find("]")).c_str(), NULL);
  }

  return axis;
}


syntax highlighted by Code2HTML, v. 0.9.1