/* ==================================================== ======== ======= *
 *
 *  xkeys.cc : key management and correspondance with VREng
 *  NOTE: this file should be common to all X-Window GUI variants
 *
 *  VREng Project [Elc::2000]
 *  Author: Eric Lecolinet
 *
 *  (C) 1999-2000 Eric Lecolinet @ ENST Paris
 *  WWW: http://www.enst.fr/~elc/ubit   Email: elc@enst.fr (subject: ubit)
 *
 * ***********************************************************************
 * COPYRIGHT NOTICE : 
 * THIS PROGRAM IS DISTRIBUTED WITHOUT ANY WARRANTY AND WITHOUT EVEN THE 
 * IMPLIED WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. 
 * 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 OF THE LICENSE, OR (AT YOUR OPTION) ANY LATER VERSION.
 * SEE FILES 'COPYRIGHT' AND 'COPYING' FOR MORE DETAILS.
 * ***********************************************************************
 */

#ifndef VRENGD

#include <X11/keysym.h>

#include "global.h"
#include "world.h"
#include "net.h"		/* NetObject */
#include "wobject.h"		/* WObject */
#include "user.h"		/* FOVY* */

#include "gui.h"
#include "guiImpl.hh"
#include "keys.h"


// *** Use this callback function for handling AUTOREPEAT Key event 
// Autorepeat is logically suppressed by annulating corresponding Press 
// and Release events.
// Principle: Release events are postponed and temporarily stored in KRmask
// They are processed when coming back to the RenderingWorkProc of the
// mainLoop if not annulated in the meantime by subsequent Press events.


void GUI::processKey(KeySym keysym, int press) {

  // converts the X keysym into a Vreng change key (vrkey) and returns
  // a keymask which is an hexa value for marking released keys in the KRmask
  int vrkey;
  long keymask = convertKey(keysym, vrkey);
  if (keymask == 0) return;    // return if null (undefined or not a vrkey)

  if (postponedKRcount < 0) {
    fprintf(stderr, "!negative KRcount => reset\n"); // should never happen!
    postponedKRmask = 0;
    postponedKRcount = 0;
  }

  if (press) {
    if (postponedKRmask & keymask) { 
      // le Press annule le Release correspondant dans le KRmask

      postponedKRmask &= ~keymask;	// remove keymask from KRmask
      postponedKRcount--;		// une touche en moins dans KRlist
      // rien d'autre a faire puisque c'etait juste une anulation
    }
    else {  // traitement normal d'un Press
      struct timeval time;
      gettimeofday(&time, NULL);
      //fprintf(stderr, "KPress change or activate Key( %d ) \n", vrkey);
      if (vrkey >= MAXKEYS || vrkey < 0) {
        return;
      }
      changeKey(vrkey, press, time.tv_sec, time.tv_usec);
    }
  }

  else { // release

    // too many keys stored ==> flush the KRlist
    if (postponedKRcount > KRBUF_MAXCOUNT) {
      fprintf(stderr, "-->too many keys stored => flush: KRmask=%lx\n", postponedKRmask);
      flushPostponedKRs();
    }

    else { // normal case: postpone until we come back to the mainLoop
      // so that this Key Release event can possibly be annulate
      // if there is a subsequent (and corresponding) Key Press event

      postponedKRmask |= keymask;	// add keymask to KRmask

      // add this event to the KRlist of postponed Key Releases
      postponedKRbuf[postponedKRcount].vrkey = vrkey;
      gettimeofday(&(postponedKRbuf[postponedKRcount].time), NULL);
      postponedKRcount++;
    }
  }
}


void GUI::flushPostponedKRs() {
    //fprintf(stderr, "-->postponed release: KRmask=%x\n", gui->KRmask);
  //PB testGrid("keys.cc : void GUI::flushPostponedKRs() : BEGIN") ;

  for (int ix = 0; ix < postponedKRcount; ix++) {
    KRKey &k = postponedKRbuf[ix];
    //fprintf(stderr, "-->changeKey( %d )\n", k.vrkey);
    changeKey(k.vrkey, FALSE, k.time.tv_sec, k.time.tv_usec);
  }
  postponedKRmask = 0;
  postponedKRcount = 0;
  //PB testGrid("keys.cc : void GUI::flushPostponedKRs() : END") ;
}


long GUI::convertKey(KeySym keysym, int &vrkey) {
  long keymask = 0;
  switch (keysym) {
  case XK_Up:		/* move forward */
    keymask = 1 << 0; vrkey = KEY_AV;
    break;
  case XK_Down:		/* move backward */
    keymask = 1 << 1; vrkey = KEY_AR;
    break;
  case XK_Left:		/* turn left */
    keymask = 1 << 2; vrkey = KEY_GA;
    break;
  case XK_Right:	/* turn right */
    keymask = 1 << 3; vrkey = KEY_DR;
    break;

  case XK_Shift_L:      //new: left translation 
    keymask = 1 << 4; vrkey = KEY_SG;
    break;
  case XK_Shift_R:      //new: right translation 
    keymask = 1 << 5; vrkey = KEY_SD;
    break;

    //NB: inversion de Prior avec Insert, idem Next et Del
  case XK_Prior:	/* move up */
    keymask = 1 << 11; vrkey = KEY_JU;
    break;
  case XK_Next:		/* move down */
    keymask = 1 << 12; vrkey = KEY_JD;
    break;
  case XK_Insert:	/* tilt down */
    keymask = 1 << 6; vrkey = KEY_MT;
    break;
  case XK_Delete:	/* tilt up */
    keymask = 1 << 7; vrkey = KEY_DE;
    break;

  case XK_space:    //new! : ACCELERATORS
  case XK_KP_Space:
  //case XK_Mode_switch:  /* key AltGr on French keyboards */
  case XK_End:
    keymask = 1 << 13; vrkey = KEY_VI;
    break;

  case XK_Home:		/* stand up */
    keymask = 1 << 8; vrkey = KEY_HZ;
    break;
  case XK_KP_Left:	/* tilt left */
    keymask = 1 << 9; vrkey = KEY_TL;  //NB: la diff avec turn left n'est pas nette!
    break;
  case XK_KP_Right:	/* tilt right */
    keymask = 1 << 10; vrkey = KEY_TR;
    break;

  case XK_KP_Home:
    /* NULL = user, 0 = original fovy */
    GUI::callVrengAction(FOVYORIGINAL);
    return 0;  // done, don't call on release

  case XK_KP_Down:
    /* NULL = user, 0 = decrease fovy */
    GUI::callVrengAction(FOVYLESS);
    return 0;  // done, don't call on release

  case XK_KP_Up:
    /* NULL = user, 0 = increase fovy */
    GUI::callVrengAction(FOVYGREATER);
    return 0;  // done, don't call on release

  case XK_equal:
  case XK_Mode_switch:  /* key AltGr on French keyboards */
    /* NULL = user, 0 = original lspeed */
    GUI::callVrengAction(LSPEEDORIGINAL);
    return 0;  // done, don't call on release

  case XK_Meta_L:
  case XK_minus:
  case XK_s:
    /* NULL = user, 0 = decrease lspeed */
    GUI::callVrengAction(LSPEEDLESS);
    return 0;  // done, don't call on release

  case XK_Meta_R:
  case XK_f:
    /* NULL = user, 0 = increase lspeed */
    GUI::callVrengAction(LSPEEDGREATER);
    return 0;  // done, don't call on release

  case XK_comma:
  case XK_period:
    /* NULL = user, 0 = original aspeed */
    GUI::callVrengAction(ASPEEDORIGINAL);
    return 0;  // done, don't call on release

  case XK_less:
  case XK_BackSpace:
    /* NULL = user, 0 = decrease aspeed */
    GUI::callVrengAction(ASPEEDLESS);
    return 0;  // done, don't call on release

  case XK_greater:
  case XK_Tab:
    /* NULL = user, 0 = increase aspeed */
    GUI::callVrengAction(ASPEEDGREATER);
    return 0;  // done, don't call on release

  case XK_u:
    keymask = 1 << 11 | 1 << 13; vrkey = KEY_JU;
    break;

  case XK_b:
  case XK_Control_L:
    GUI::callVrengAction(BULLETUSER);
    return 0;  // done, don't call on release

  case XK_d:
  case XK_Alt_L:
    GUI::callVrengAction(DARTUSER);
    return 0;  // done, don't call on release

  default:
    return 0; // == 0 => undefined key
    break;
  }

  return keymask;
}

#endif /* !VRENGD */
