/*
 *    Ygl: Run 2d-GL programs with standard X11 routines.
 *    (C) Fred Hucht 1993-95
 *    EMail: fred@thp.Uni-Duisburg.DE
 */

#include "header.h"
#include <X11/keysym.h>

#define RedrawMask (ExposureMask|StructureNotifyMask)

static Ulong YglButtonMask = 0;

#define MAP_LEN 256
#define MAP_BIT 0x1000
static long  map[MAP_LEN];
static int   KeyFlag   = False;
static int   KeybdFlag = False;

static void  init_map(void);
static int   change_map(Device dev, int on_off);
static void  set_YglEventMask(void);
static Bool  more_redraw(Display*, XEvent*, char *);
static int   check_more_redraw(int window);
static Int32 q_next(Int16 *val, int parent);

/* XChangeActivePointerGrab only understands these: */
#define XCAPG_MASK (ButtonPressMask|ButtonReleaseMask|\
		    EnterWindowMask|LeaveWindowMask|\
		    Button1MotionMask|Button2MotionMask|\
		    Button3MotionMask|Button4MotionMask|\
		    Button5MotionMask|PointerMotionHintMask|\
		    PointerMotionMask|ButtonMotionMask|KeymapStateMask)

static int change_map(Device dev, int on_off) {
  int code, found = False;
  for(code = 0; code < MAP_LEN; code++) /* several codes may return same keysym (eg MacX) */
    if(dev == (map[code] & (MAP_BIT-1))) { /* found */
      found = True;
      if(on_off) map[code] |=  MAP_BIT;
      else       map[code] &= ~MAP_BIT;
#ifdef DEBUG
      fprintf(stderr, "change_map: dev = %d, code = %d, new map[code] = %d\n", dev, code, map[code]);
#endif
    }
  return(found);
}

static void init_map(void) {
  int code;
  static int inited = False;
  if(inited) return;
  inited = True;
  for(code = 0; code < MAP_LEN; code++) {
    KeySym ks = XKeycodeToKeysym(D, code, 0);
    switch(ks) {
      /* main keys */
    case XK_a:		 map[code] = AKEY;		break;
    case XK_b:		 map[code] = BKEY;		break;
    case XK_c:		 map[code] = CKEY;		break;
    case XK_d:		 map[code] = DKEY;		break;
    case XK_e:		 map[code] = EKEY;		break;
    case XK_f:		 map[code] = FKEY;		break;
    case XK_g:		 map[code] = GKEY;		break;
    case XK_h:		 map[code] = HKEY;		break;
    case XK_i:		 map[code] = IKEY;		break;
    case XK_j:		 map[code] = JKEY;		break;
    case XK_k:		 map[code] = KKEY;		break;
    case XK_l:		 map[code] = LKEY;		break;
    case XK_m:		 map[code] = MKEY;		break;
    case XK_n:		 map[code] = NKEY;		break;
    case XK_o:		 map[code] = OKEY;		break;
    case XK_p:		 map[code] = PKEY;		break;
    case XK_q:		 map[code] = QKEY;		break;
    case XK_r:		 map[code] = RKEY;		break;
    case XK_s:		 map[code] = SKEY;		break;
    case XK_t:		 map[code] = TKEY;		break;
    case XK_u:		 map[code] = UKEY;		break;
    case XK_v:		 map[code] = VKEY;		break;
    case XK_w:		 map[code] = WKEY;		break;
    case XK_x:		 map[code] = XKEY;		break;
    case XK_y:		 map[code] = YKEY;		break;
    case XK_z:		 map[code] = ZKEY;		break;
    case XK_0:		 map[code] = ZEROKEY;		break;
    case XK_1:		 map[code] = ONEKEY;		break;
    case XK_2:		 map[code] = TWOKEY;		break;
    case XK_3:		 map[code] = THREEKEY;		break;
    case XK_4:		 map[code] = FOURKEY;		break;
    case XK_5:		 map[code] = FIVEKEY;		break;
    case XK_6:		 map[code] = SIXKEY;		break;
    case XK_7:		 map[code] = SEVENKEY;		break;
    case XK_8:		 map[code] = EIGHTKEY;		break;
    case XK_9:		 map[code] = NINEKEY;		break;
    case XK_apostrophe:	 map[code] = QUOTEKEY;		break;
    case XK_backslash:	 map[code] = BACKSLASHKEY;	break;
    case XK_bracketleft: map[code] = LEFTBRACKETKEY;	break;
    case XK_bracketright:map[code] = RIGHTBRACKETKEY;	break;
    case XK_comma:	 map[code] = COMMAKEY;		break;
    case XK_equal:	 map[code] = EQUALKEY;		break;
    case XK_grave:	 map[code] = ACCENTGRAVEKEY;	break;
    case XK_minus:	 map[code] = MINUSKEY;		break;
    case XK_period:	 map[code] = PERIODKEY;	break;
    case XK_semicolon:	 map[code] = SEMICOLONKEY;	break;
    case XK_space:	 map[code] = SPACEKEY;		break;
      
    case XK_BackSpace:	 map[code] = BACKSPACEKEY;	break;
    case XK_Escape:	 map[code] = ESCKEY;		break;
    case XK_Return:	 map[code] = RETKEY;		break;
    case XK_Tab:	 map[code] = TABKEY;		break;
      /* modifiers */
    case XK_Shift_L:	 map[code] = LEFTSHIFTKEY;	break;
    case XK_Shift_R:	 map[code] = RIGHTSHIFTKEY;	break;
    case XK_Alt_L:	 map[code] = LEFTALTKEY;	break;
    case XK_Mode_switch:	
    case XK_Alt_R:	 map[code] = RIGHTALTKEY;	break;
    case XK_Control_L:	 map[code] = LEFTCTRLKEY;	break;
    case XK_Execute:	
    case XK_Control_R:	 map[code] = RIGHTCTRLKEY;	break;
    case XK_Num_Lock:	 map[code] = NUMLOCKKEY;	break;
    case XK_Caps_Lock:	
    case XK_Shift_Lock:	 map[code] = CAPSLOCKKEY;	break;
      /* cursor control */
    case XK_Delete:	 map[code] = DELKEY;		break;
    case XK_Down:	 map[code] = DOWNARROWKEY;	break;
    case XK_End:	 map[code] = ENDKEY;		break;
    case XK_Home:	 map[code] = HOMEKEY;		break;
    case XK_Insert:	 map[code] = INSERTKEY;	break;
    case XK_Left:	 map[code] = LEFTARROWKEY;	break;
    case XK_Next:	 map[code] = PAGEDOWNKEY;	break;
    case XK_Prior:	 map[code] = PAGEUPKEY;	break;
    case XK_Right:	 map[code] = RIGHTARROWKEY;	break;
    case XK_Up:		 map[code] = UPARROWKEY;	break;
      /* keypad */
    case XK_KP_0:	 map[code] = PAD0;		break;
    case XK_KP_1:	 map[code] = PAD1;		break;
    case XK_KP_2:	 map[code] = PAD2;		break;
    case XK_KP_3:	 map[code] = PAD3;		break;
    case XK_KP_4:	 map[code] = PAD4;		break;
    case XK_KP_5:	 map[code] = PAD5;		break;
    case XK_KP_6:	 map[code] = PAD6;		break;
    case XK_KP_7:	 map[code] = PAD7;		break;
    case XK_KP_8:	 map[code] = PAD8;		break;
    case XK_KP_9:	 map[code] = PAD9;		break;
    case XK_KP_F1:	 map[code] = PADPF1;		break;
    case XK_KP_F2:	 map[code] = PADPF2;		break;
    case XK_KP_F3:	 map[code] = PADPF3;		break;
    case XK_KP_F4:	 map[code] = PADPF4;		break;
    case XK_KP_Add:	 map[code] = PADPLUSKEY;	break;
    case XK_KP_Decimal:	 map[code] = PADPERIOD;	break;
    case XK_KP_Enter:	 map[code] = PADENTER;		break;
    case XK_KP_Multiply: map[code] = PADASTERKEY;	break;
    case XK_KP_Separator:map[code] = PADCOMMA;		break;
    case XK_KP_Subtract: map[code] = PADMINUS;		break;
      /* function keys */
    case XK_F1:		 map[code] = F1KEY;		break;
    case XK_F2:		 map[code] = F2KEY;		break;
    case XK_F3:		 map[code] = F3KEY;		break;
    case XK_F4:		 map[code] = F4KEY;		break;
    case XK_F5:		 map[code] = F5KEY;		break;
    case XK_F6:		 map[code] = F6KEY;		break;
    case XK_F7:		 map[code] = F7KEY;		break;
    case XK_F8:		 map[code] = F8KEY;		break;
    case XK_F9:		 map[code] = F9KEY;		break;
    case XK_F10:	 map[code] = F10KEY;		break;
    case XK_F11:	 map[code] = F11KEY;		break;
    case XK_F12:	 map[code] = F12KEY;		break;
    case XK_F13:		
    case XK_Print:	 map[code] = PRINTSCREENKEY;	break;
    case XK_F14:		
    case XK_Scroll_Lock: 
    case XK_Cancel:	 map[code] = SCROLLLOCKKEY;	break;
    case XK_F15:		
    case XK_Pause:	 map[code] = PAUSEKEY;		break;
      
    default:		 map[code] = 0;		break;
    }
#if DEBUG > 2
    if(ks != 0 || map[code] != 0) 
      fprintf(stderr, "code = %d, XK = 0x%x, map = %d\n", code, ks, map[code]);
#endif
  }
}

static void set_YglEventMask(void) {
  int i;
  
  /* To change event mask even when a button is pressed: */
  XChangeActivePointerGrab(D, Ygl.EventMask & XCAPG_MASK, None, CurrentTime);
  
#ifdef DEBUG
  fprintf(stderr, "set_YglEventMask: EventMask = 0x%x, KeyFlag = %d\n",
	  Ygl.EventMask, KeyFlag);
#endif
  
  for(i = 1; i < Ygl.NextWindow; i++) 
    if(Ygl.Windows[i].main != 0) {
      XSelectInput(D, Ygl.Windows[i].main, Ygl.EventMask);
      XSetWMProtocols(D, Ygl.Windows[i].main, &Ygl.wm_dw, Ygl.wm_dw_flag?1:0);
    }
}

static int YglTie[3][2] = {{0,0},{0,0},{0,0}}; /* ties to left, middle, right button */

void tie(Device button, Device val1, Device val2) {
  int tieb;

  switch(button) {
  case LEFTMOUSE:   tieb = 0; break;
  case MIDDLEMOUSE: tieb = 1; break;
  case RIGHTMOUSE:  tieb = 2; break;
  default:          fprintf(stderr, "Ygl: tie: first argument is not a button: %d.\n", button); return;
  }

  switch(val1) {
  case MOUSEX: break;
  case MOUSEY: break;
  default:     fprintf(stderr, "Ygl: tie: second argument is not a valuator: %d.\n", val1); return;
  }
  
  switch(val2) {
  case MOUSEX: break;
  case MOUSEY: break;
  default:     fprintf(stderr, "Ygl: tie: third argument is not a valuator: %d.\n", val2); return;
  }
  
  YglTie[tieb][0] = val1;
  YglTie[tieb][1] = val2;
}  

void qdevice(Device dev) {
  I("qdevice");

  init_map();

  switch(dev) {
  case WINCLOSE:    
    fprintf(stderr, "Ygl: qdevice: device WINCLOSE not longer supported. Using WINQUIT instead...\n");
  case WINQUIT:      /* This is the event GL sends for WM_DELETE_WINDOW... */
                     Ygl.wm_dw_flag = True;              break;
  case REDRAW:       Ygl.EventMask |= RedrawMask;        break;
  case INPUTCHANGE:  Ygl.EventMask |= EnterLeaveMask;    break;
  case MOUSEX:      
  case MOUSEY:       Ygl.EventMask |= PointerMotionMask; break;
  case LEFTMOUSE:    YglButtonMask |= Button1Mask;       break;
  case MIDDLEMOUSE:  YglButtonMask |= Button2Mask;       break;
  case RIGHTMOUSE:   YglButtonMask |= Button3Mask;       break;
  case KEYBD:        KeybdFlag   = True;                 break;
    
  default:          
    if(change_map(dev, True)) {
      KeyFlag = True;
    } else {
      fprintf(stderr, "Ygl: qdevice: unknown device: %d.\n", dev); return;
    }
    break;
  }
  
  if(YglButtonMask) Ygl.EventMask |= ButtonPressMask|ButtonReleaseMask;
  
  if(KeybdFlag || KeyFlag) Ygl.EventMask |= KeyPressMask|KeyReleaseMask;
  
  set_YglEventMask();
}    

void unqdevice(Device dev) {
  int i;
  I("unqdevice");
  switch(dev) {
  case WINCLOSE:    
    fprintf(stderr, "Ygl: uqdevice: device WINCLOSE not longer supported. Using WINQUIT instead...\n");
  case WINQUIT:      /* This is the event GL sends for WM_DELETE_WINDOW... */
                     Ygl.wm_dw_flag = False;              break;
  case REDRAW:       Ygl.EventMask &= ~RedrawMask;        break;
  case INPUTCHANGE:  Ygl.EventMask &= ~EnterLeaveMask;    break;
  case MOUSEX:      
  case MOUSEY:       Ygl.EventMask &= ~PointerMotionMask; break;
  case LEFTMOUSE:    YglButtonMask &= ~Button1Mask;       break;
  case MIDDLEMOUSE:  YglButtonMask &= ~Button2Mask;       break;
  case RIGHTMOUSE:   YglButtonMask &= ~Button3Mask;       break;
  case KEYBD:        KeybdFlag   = False;                 break;

  default:
    if(!change_map(dev, False)) {
      fprintf(stderr, "Ygl: unqdevice: unknown device: %d.\n", dev); return;
    }
    break;
  }
  
  if (!YglButtonMask) Ygl.EventMask &= ~(ButtonPressMask|ButtonReleaseMask);

  KeyFlag = False;
  for(i = 0; i < MAP_LEN; i++) KeyFlag = KeyFlag || (map[i] & MAP_BIT);
  
  if(!(KeybdFlag || KeyFlag)) Ygl.EventMask &= ~(KeyPressMask|KeyReleaseMask);
  
  set_YglEventMask();
}    

static Int16 mousex = -1, mousey = -1, mousexchanged = False, mouseychanged = False;
static Int16 keyevent = 0;

void qreset(void) {
  I("qreset");
  XSync(D, True);
  mousex = -1; mousey = -1;
  mousexchanged = False;
  mouseychanged = False;
  keyevent = 0;
}

static Bool more_redraw(Display *dpy, XEvent *e, char *rc) {
  int *r = (int*) rc;
  int ret;
  if(((e->type == Expose) || (e->type == ConfigureNotify)) && e->xany.window == r[0]) {
    r[2] = True; /* found Expose or ConfigureNotify event for same window */
  }
  ret = r[2] || (--r[1] == 0); /* return True if found or queue empty */
#ifdef DEBUG
  fprintf(stderr, "more_redraw: r={%d,%d,%d}, ret=%d\n", r[0],r[1],r[2],ret);
#endif
  return(ret);
}

static int check_more_redraw(int window) {
  XEvent p;
  int args[3];
  args[0] = window;
  args[1] = XPending(D);
  args[2] = False;
  if(args[1]) {
#ifdef DEBUG
    fprintf(stderr, "check_more_redraw: Calling XPeekIfEvent\n");
#endif
    XPeekIfEvent(D, &p, more_redraw, (char*)args);
  }
  return(args[2]);
}

#define QREAD 1
#define QTEST 2

static Int32 q_next(Int16 *val, int parent) {
  XEvent e;
  Int32 dev = -1, dev2;
  int tieb;
  char key, *pname = (parent == QTEST) ? "qtest" : "qread";
  
  I(pname);
  
  while(dev == -1) {
    if(mousexchanged) {		/* Hack for mouse events */
      dev  = MOUSEX;
      *val = mousex;
      if(parent == QREAD) mousexchanged = False;
    } else if(mouseychanged) {	/* Hack for mouse events */
      dev  = MOUSEY;
      *val = YglScreenHeight - mousey - 1;
      if(parent == QREAD) mouseychanged = False;
    } else if(keyevent > 0) {	/* Hack for reporting both KEYBD and ANYKEY device */
      dev  = keyevent;
      *val = 1; /* Must be 1 (KeyPress) */
      if(parent == QREAD) keyevent = 0;
    } else if(parent == QTEST && XPending(D) == 0) {
      dev = 0;
    } else {
      
      if(parent == QREAD) XNextEvent(D, &e);
      else                XPeekEvent(D, &e);
      
#ifdef DEBUG
      fprintf(stderr, "%s: e.type = %d, XPending returns %d\n", pname, e.type, XPending(D));
#endif
      
      switch(e.type) {
	
      case ConfigureNotify:
      case Expose:
	if(parent == QTEST) {
	  dev = REDRAW;
	} else if(e.type == Expose && e.xexpose.count != 0) { /* ignore */
#ifdef DEBUG
	  fprintf(stderr, "qread: e.xexpose.count = %d\n", e.xexpose.count);
#endif
	} else if(check_more_redraw(e.xany.window)) {	/* more Expose events for the same window in queue? */
#ifdef DEBUG
	  fprintf(stderr, "qread: More Expose or ConfigureNotify events for same window in queue, ignoring this one...\n");
#endif
	} else {
	  dev  = REDRAW;
	  *val = Ygl.x2gl_wid(e.xany.window);
	}
	break;
	
      case EnterNotify:
	dev  = INPUTCHANGE;
	*val = Ygl.x2gl_wid(e.xany.window);
	break;
	
      case LeaveNotify:
	dev  = INPUTCHANGE;
	*val = 0;
	break;
	
      case MotionNotify:
#ifdef DEBUG
	fprintf(stderr, "%s: e.xmotion.x_root = %d, e.xmotion.y_root = %d\n",
		/**/  pname, e.xmotion.x_root,      e.xmotion.y_root);
#endif
	if(parent == QTEST) {
	  if(mousex != e.xmotion.x_root)
	    dev = MOUSEX;	/* if both values have changed, report x first... */
	  else
	    dev = MOUSEY;
	} else {
	  if(mousey != e.xmotion.y_root) {
	    mousey = e.xmotion.y_root;
	    mouseychanged = True;
	  }
	  if(mousex != e.xmotion.x_root) {
	    dev  = MOUSEX;
	    *val = mousex = e.xmotion.x_root;
	  } 
	}
	break;
	
      case KeyPress:
      case KeyRelease: /* Note: KeyReleases are not reported as KEYBD events */
#ifdef DEBUG
	fprintf(stderr, "%s: e.xkey.keycode = %d, map[%d] = %d\n",
		pname, e.xkey.keycode, e.xkey.keycode, map[e.xkey.keycode]);
#endif
	if(KeybdFlag && (e.type == KeyPress) && 1 == XLookupString(&e.xkey, &key, 1, NULL, NULL)) {
	  /* return code */
	  dev  = KEYBD;
	  *val = (Int16)key;
	}
	if(KeyFlag && (dev2 = map[e.xkey.keycode]) & MAP_BIT ) { /* Individual keys requested? */
	  dev2 &= (MAP_BIT-1);
	  if(dev == KEYBD) {	/* first report KEYBD event */
	    keyevent = dev2;
	  } else {
	    dev  = dev2;
	    *val = (e.type == KeyPress) ? 1 : 0;
	  }
	}
      	break;
	
      case ButtonPress:
      case ButtonRelease:
	switch(e.xbutton.button) {
	case Button1:
	  if(YglButtonMask & Button1Mask) {
	    dev = LEFTMOUSE;
	    if(parent == QREAD) tieb = 0;
	  }
	  break;
	case Button2:
	  if(YglButtonMask & Button2Mask) {
	    dev = MIDDLEMOUSE;
	    if(parent == QREAD) tieb = 1;
	  }
	  break;
	case Button3:
	  if(YglButtonMask & Button3Mask) {
	    dev = RIGHTMOUSE;
	    if(parent == QREAD) tieb = 2;
	  }
	  break;
	default:
	  fprintf(stderr, "Ygl: %s: unknown button: %d.\n",
		  pname, e.xbutton.button);
	  break;
	}
	
	if(parent == QREAD && dev != -1) {
	  *val = (e.type == ButtonPress) ? 1 : 0;
	  
	  switch(YglTie[tieb][0]) {
	  case MOUSEX: mousexchanged = True; mousex = e.xbutton.x_root; break;
	  case MOUSEY: mouseychanged = True; mousey = e.xbutton.y_root; break;
	  }
	  switch(YglTie[tieb][1]) {
	  case MOUSEX: mousexchanged = True; mousex = e.xbutton.x_root; break;
	  case MOUSEY: mouseychanged = True; mousey = e.xbutton.y_root; break;
	  }
	}
	break;
	
      case ClientMessage: 
#ifdef DEBUG
	fprintf(stderr, "%s: e.xclient.message_type = %d\n",
		pname, e.xclient.message_type);
#endif
	dev  = WINQUIT;
	*val = Ygl.x2gl_wid(e.xany.window);
	break;
	
      case CirculateNotify:
      case CreateNotify:
      case DestroyNotify:
      case GravityNotify:
      case ReparentNotify:
      case MapNotify:
      case UnmapNotify:
	/* Ignore these Events... */
	break;
	
      default:
	fprintf(stderr, "Ygl: %s: unknown event %d for window %d received.\n",
		pname, e.type, Ygl.x2gl_wid(e.xany.window));
	break;
      } /* end switch */
      
      if(dev == -1 && parent == QTEST) {
#ifdef DEBUG
	fprintf(stderr, "qtest: eating %s event. e.type = %d\n", e.type == 2 ? "KeyPress" : "unknown", e.type);
#endif
	XNextEvent(D, &e); /* read and ignore unreported events so they don't block the queue */
      }
      
      if(dev != -1 && parent == QREAD) {
#ifdef DEBUG
	fprintf(stderr, "qread: saving event.\n");
#endif
	Ygl.lastreadevent = e;
      }
    }
  }
#ifdef DEBUG
  if(dev != 0) fprintf(stderr, "%s: returning dev = %d, *val = %d\n",
		       pname, dev, *val);
#endif
  return(dev);
}

Int32 qtest(void) { 
  Int32 dev; Int16 val;
  dev = q_next(&val, QTEST);
  return(dev);
}

Int32 qread(Int16 *val) {
  Int32 dev;
  dev = q_next(val, QREAD);
  return(dev);
}

void qenter(Int16 dev, Int16 val) {
  XEvent e;
  I("qenter");
  switch(dev) {
  case REDRAW:       e.type = Expose; 
                     e.xexpose.window = (val > 0 && val < Ygl.NextWindow) ? Ygl.Windows[val].main : 0;
                     e.xexpose.count = 0; break;

  case INPUTCHANGE:  fprintf(stderr, "Ygl: qenter: unsupported device: INPUTCHANGE.\n") ; return; break;
    /* e.type = EnterNotify; e.xcrossing.window = val; break; */
  case KEYBD:        fprintf(stderr, "Ygl: qenter: unsupported device: KEYBD.\n")       ; return; break;
  case MENUBUTTON:   fprintf(stderr, "Ygl: qenter: unsupported device: MENUBUTTON.\n")  ; return; break;
  default:           fprintf(stderr, "Ygl: qenter: unknown device: %d.\n", dev)         ; return; break;
  }
  XPutBackEvent(D, &e);
}
