/*
 * Rubik's clock.
 *
 * This file is public domain, I just hate copy[left/right]s.
 *-------------
 * the event handling stuff file
 */

#include "event.h"

#include <X11/Xlib.h>
#include <X11/keysym.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

#include "clock.h"
#include "screen.h"
#include "solver.h"

/* note that the timer under linux will return _after_ the wanted time, but we don't know when,
 * linux is not a realtime os, we don't have an upper bound for timers. Note too that this code
 * will return if an X event appears _before_ the timeout, so the caller has to check for a timeout
 * when this function returns, by getting the time and comparing with the initial one.
 */
void wait_next_event(EVENT *ev, SCREEN *s)
{
  int ret;
  fd_set set, dum;
  struct timeval timer;
  XEvent e;

  /* let's flush the screen first */
  XFlush(s->d);

  ev->type=0;

  FD_ZERO(&dum);

  FD_ZERO(&set);
  FD_SET(ConnectionNumber(s->d), &set);

  if (ev->timer) {
    timer.tv_sec=ev->timer/1000;
    timer.tv_usec=(ev->timer%1000)*1000;
    ret=select(ConnectionNumber(s->d)+1, &set, &dum, &dum, &timer);
  } else
    ret=select(ConnectionNumber(s->d)+1, &set, &dum, &dum, 0);

  if (ret==-1) {
    perror("wait_next_event select error");
    return;
  }

  /* we always set the TIMEOUT flag if there is a timer, up to the caller to check if there
   * really is a timeout
   */
  if (ev->timer)
    ev->type=TIMEOUT;
  if (!FD_ISSET(ConnectionNumber(s->d), &set))
    return;

  /* all the following events are generated by the human */
  ev->generator=HUMAN;

  /* ok, no error, no timeout, let's handle all the XEvents */
  /* I love gotos, sorry :) */
next_event:
  /* well, this code is not correct, we may lose events, because some events might appear
   * several times before we return (but highly unprobable)
   */
  /* we too might get lotsa XEvents and never return, but once again, it's highly unprobable */
  if (XCheckMaskEvent(s->d, -1L, &e)==False)
    return;
  switch(e.type) {
    case Expose : {
      /* we gotta check window's size change (a simple GetGeometry is perfect) */
      Window wdum;
      unsigned int dum;
      XGetGeometry(s->d, s->w, &wdum, (int *)&dum, (int *)&dum, &s->sizex, &s->sizey, &dum, &dum);
      
      ev->type|=REDRAW;
      goto next_event;
    }
    case MotionNotify : {
      XMotionEvent *e1=(XMotionEvent *)&e;
      ev->mousex=e1->x;
      ev->mousey=e1->y;
      ev->type|=MOUSEMOVE;
      goto next_event;
    }
    case ButtonPress : {
      /* we theorically could have several button pressed, but I guess it will be very hard to generate... */
      XButtonEvent *e1=(XButtonEvent *)&e;
      if ((ev->which_button=mouse_on_button(s, e1->x, e1->y))!=-1) {
	ev->type|=BUTTON;
	goto next_event;
      }
      /* same remark as above */
      if ((ev->which_corner=mouse_on_arrow(s, e1->x, e1->y))!=-1) {
	ev->type|=CORNER;
	goto next_event;
      }
      if (mouse_on_turn(s, e1->x, e1->y)!=-1) {
	ev->type|=TURN;
	goto next_event;
      }
      if (mouse_on_quit(s, e1->x, e1->y)!=-1) {
	ev->type|=QUIT;
	goto next_event;
      }
      goto next_event;
    }
    case KeyPress : {
      int d;
      switch(d=XLookupKeysym((XKeyEvent *)&e, 0)) {
	case XK_space :
	case 't' :
	case 'T' :
	  ev->type|=TURN;
	  goto next_event;
	case '4' :
	case 'y' :
	case 'Y' :
	  ev->type|=BUTTON;
	  ev->which_button=0;
	  goto next_event;
	case 'u' :
	case 'U' :
	case '5' :
	  ev->type|=BUTTON;
	  ev->which_button=1;
	  goto next_event;
	case 'h' :
	case 'H' :
	case '1' :
	  ev->type|=BUTTON;
	  ev->which_button=2;
	  goto next_event;
	case 'j' :
	case 'J' :
	case '2' :
	  ev->type|=BUTTON;
	  ev->which_button=3;
	  goto next_event;
	case XK_Escape :
	case 'q' :
	case 'Q' :
	  ev->type|=QUIT;
	  goto next_event;
	case 'b' :
	case 'B' :
	case XK_Left : {
	  int which[]={4, 6, 2, 0};
	  ev->type|=CORNER;
	  ev->which_corner=which[s->current_corner];
	  goto next_event;
	}
	case 'n' :
	case 'N' :
	case XK_Right : {
 	  int which[]={5, 7, 3, 1};
	  ev->type|=CORNER;
	  ev->which_corner=which[s->current_corner];
	  goto next_event;
	}
	case XK_Up : {
	  int next[]={1, 3, 0, 2};
	  ev->which_select_corner=next[s->current_corner];
	  ev->type|=SELECT_CORNER;
	  goto next_event;
	}
	case XK_Down : {
	  int next[]={2, 0, 3, 1};
	  ev->which_select_corner=next[s->current_corner];
	  ev->type|=SELECT_CORNER;
	  goto next_event;
	}
	case 'e' :
	case 'E' :
	  ev->which_select_corner=0;
	  ev->type|=SELECT_CORNER;
	  goto next_event;
	case 'r' :
	case 'R' :
	  ev->which_select_corner=1;
	  ev->type|=SELECT_CORNER;
	  goto next_event;
	case 'd' :
	case 'D' :
	  ev->which_select_corner=2;
	  ev->type|=SELECT_CORNER;
	  goto next_event;
	case 'f' :
	case 'F' :
	  ev->which_select_corner=3;
	  ev->type|=SELECT_CORNER;
	  goto next_event;
	/* some hack functions */
	case XK_F1 :
	  ev->type|=F1;
	  goto next_event;
	case XK_F2 :
	  ev->type|=F2;
	  goto next_event;
	case XK_F3 :
	  ev->type|=F3;
	  goto next_event;
	case XK_F4 :
	  ev->type|=F4;
	  goto next_event;
      }
      goto next_event;
    }
  }
}

void handle_event(EVENT *ev, CLOCK *c, SCREEN *s)
{
  if (ev->type & QUIT) {
    if (c->mode == NORMAL) {
      c->mode=END;
      return;
    }
    if (c->mode == SOLVER)
      c->mode=SOLVER_INTERRUPTED;
  }

  if (ev->type & REDRAW)
    draw_all(c, s);

  if (ev->type & MOUSEMOVE)
    draw_mouse(c, s, ev->mousex, ev->mousey);

  if (ev->type & TURN)
    if (c->mode == NORMAL || ev->generator==MACHINE)
      return_clock(c, s);

  if (ev->type & BUTTON)
    if (c->mode == NORMAL || ev->generator==MACHINE)
      push_button(c, ev->which_button, s);

  if (ev->type & CORNER) {
    int corn[]={3, 3, 2, 2, 0, 0, 1, 1};
    if (c->mode == NORMAL || ev->generator==MACHINE)
      turn_corner(c, corn[ev->which_corner], ev->which_corner & 1, s);
  }

  if (ev->type & SELECT_CORNER)
    select_corner(s, ev->which_select_corner);

  if (ev->type & F2)
    if (c->mode == NORMAL) {
      init_clock(c, s);
      draw_all(c, s);
    }

  if (ev->type & F3)
    if (c->mode == NORMAL) {
      randomize_clock(c);
      draw_all(c, s);
    }

  /* this must come last, we must have checked all the other events before this one */
  if (ev->type & F1 || ev->type & F4)
    if (c->mode == NORMAL) {
      c->mode=SOLVER;
      /* is it a good design to have a call to solve here ? */
      solve(c, s);
      c->mode=NORMAL;
    }
}
