#include <config.h>
#include "xwrits.h"
#include <stdlib.h>
#include <assert.h>


/* wait for break */

static struct timeval wait_over_time;

static int
wait_x_loop(XEvent *e, const struct timeval *now)
{
  struct timeval diff;

  if (e->type == KeyPress || e->type == MotionNotify
      || e->type == ButtonPress) {
    xwSUBTIME(diff, *now, last_key_time);
    last_key_time = *now;

    /* if check_idle is on, long idle periods are the same as breaks */
    if (check_idle && xwTIMEGEQ(diff, idle_time))
      return TRAN_REST;

    /* if check_quota is on, mini-breaks add up over time */
    if (check_quota && xwTIMEGEQ(diff, quota_time)) {
      xwADDTIME(quota_allotment, quota_allotment, diff);
      if (check_idle && xwTIMEGEQ(quota_allotment, idle_time))
	return TRAN_REST;
    }

    /* wake up if time to warn */
    return (xwTIMEGEQ(*now, wait_over_time) ? TRAN_WARN : 0);

  } else
    return 0;
}

static int
adjust_wait_time(struct timeval *wait_began_time, const struct timeval *type_time)
     /* Adjust the time to wake up to reflect the length of the break, if
        under check_quota. Want to be able to type for slightly longer if
        you've been taking mini-breaks. */
{
  struct timeval this_break_time;
  struct timeval break_end_time;
  assert(check_quota);

  /* Find the time when this break should end = beginning of wait + type delay
     + break delay */
  xwADDTIME(break_end_time, *wait_began_time, *type_time);
  xwADDTIME(break_end_time, break_end_time, ocurrent->break_time);

  /* Find the length of this break */
  xwSUBTIME(this_break_time, ocurrent->break_time, quota_allotment);
  if (xwTIMEGEQ(ocurrent->min_break_time, this_break_time))
    this_break_time = ocurrent->min_break_time;

  /* Subtract to find when we should start warning */
  xwSUBTIME(break_end_time, break_end_time, this_break_time);

  /* Check against wait_over_time; if <=, wait is over */
  if (xwTIMEGEQ(wait_over_time, break_end_time))
    return TRAN_WARN;

  /* Set wait_over_time and return 0 -- we still need to wait */
  wait_over_time = break_end_time;
  return 0;
}

int
wait_for_break(const struct timeval *type_time)
{
  int val, i;
  struct timeval wait_began_time;

  /* Schedule wait_over_time */
  xwGETTIME(wait_began_time);
  xwADDTIME(wait_over_time, wait_began_time, *type_time);
  if (check_quota)
    xwSETTIME(quota_allotment, 0, 0);

  /* Pretend there's been a keystroke */
  last_key_time = wait_began_time;

  /* Clear slideshows */
  /* Do this now so later set_slideshows start from scratch. */
  for (i = 0; i < nports; i++) {
    set_all_slideshows(ports[i]->hands, 0);
    set_all_slideshows(ports[i]->icon_hands, 0);
  }

  val = 0;
  while (val != TRAN_WARN && val != TRAN_REST) {
    /* If !check_idle, we want to appear even if no keystroke happens, so
       schedule an alarm */
    if (!check_idle) {
      Alarm *a = new_alarm(A_AWAKE);
      a->timer = wait_over_time;
      unschedule(A_AWAKE);
      schedule(a);
    }

    /* Wait */
    val = loopmaster(0, wait_x_loop);
    if (val == TRAN_AWAKE) val = TRAN_WARN; /* patch A_AWAKE case */

    /* Adjust the wait time if necessary */
    assert(val == TRAN_WARN || val == TRAN_REST);
    if (val == TRAN_WARN && check_quota)
      val = adjust_wait_time(&wait_began_time, type_time);
  }

  unschedule(A_FLASH | A_AWAKE);
  assert(val == TRAN_WARN || val == TRAN_REST);
  return val;
}


/* rest */

static int current_cheats;
static struct timeval break_over_time;

static int
rest_x_loop(XEvent *e, const struct timeval *now)
{
  /* If the break is over, wake up. */
  if (xwTIMEGEQ(*now, break_over_time))
    return TRAN_AWAKE;

  if (e->type == Xw_DeleteWindow && active_hands() == 0)
    /* Window manager deleted last xwrits window. Consider break over. */
    return TRAN_CANCEL;
  else if (e->type == KeyPress || e->type == MotionNotify
	   || e->type == ButtonPress) {
    last_key_time = *now;
    current_cheats++;
    return (current_cheats > max_cheats ? TRAN_FAIL : 0);
  } else
    return 0;
}

void
calculate_break_time(struct timeval *break_over_time, const struct timeval *now)
{
  struct timeval this_break_time;

  /* determine length of this break. usually break_time; can be different if
     check_quota is on */
  this_break_time = ocurrent->break_time;
  if (check_quota) {
    xwSUBTIME(this_break_time, this_break_time, quota_allotment);
    if (xwTIMEGEQ(ocurrent->min_break_time, this_break_time))
      this_break_time = ocurrent->min_break_time;
  }

  /* determine when to end the break */
  if (!check_idle)
    xwADDTIME(*break_over_time, *now, this_break_time);
  else
    xwADDTIME(*break_over_time, last_key_time, this_break_time);
}

int
rest(void)
{
  struct timeval now;
  Alarm *a;
  int tran, i;

  /* set up pictures */
  /* Do this first so later set_slideshows start from scratch. */
  for (i = 0; i < nports; i++) {
    set_all_slideshows(ports[i]->hands, resting_slideshow);
    set_all_slideshows(ports[i]->icon_hands, resting_icon_slideshow);
    find_one_hand(ports[i], 1);
  }
  current_cheats = 0;

  /* calculate time when break is over */
  xwGETTIME(now);
  calculate_break_time(&break_over_time, &now);

  /* if break already over, return */
  if (xwTIMEGEQ(now, break_over_time))
    return TRAN_AWAKE;

  /* schedule wakeup */
  a = new_alarm(A_AWAKE);
  a->timer = break_over_time;
  schedule(a);

  /* reschedule mouse position query timing: allow 5 seconds for people to
     jiggle the mouse before we save its position */
  if (check_mouse) {
    a = grab_alarm_data(A_MOUSE, 0, 0);
    a->timer = now;
    a->timer.tv_sec += 5;
    schedule(a);
    for (i = 0; i < nports; i++)
      ports[i]->last_mouse_root = None;
  }

  if (ocurrent->break_clock) {
    clock_zero_time = break_over_time;
    draw_all_clocks(&now);
    a = new_alarm(A_CLOCK);
    xwADDTIME(a->timer, now, clock_tick);
    schedule(a);
  }

  for (i = 0; i < nports; i++)
    XFlush(ports[i]->display);
  tran = loopmaster(0, rest_x_loop);

  unschedule(A_FLASH | A_AWAKE | A_CLOCK);
  erase_all_clocks();
  assert(tran == TRAN_CANCEL || tran == TRAN_AWAKE || tran == TRAN_FAIL);
  return tran;
}


/* ready */

static int
ready_x_loop(XEvent *e, const struct timeval *now)
{
  if (e->type == KeyPress || e->type == MotionNotify
      || e->type == ButtonPress) {
    /* if they typed, disappear automatically */
    last_key_time = *now;
    return 1;
  } else
    return 0;
}

void
ready(void)
{
  int i;
  for (i = 0; i < nports; i++) {
    set_all_slideshows(ports[i]->hands, ready_slideshow);
    set_all_slideshows(ports[i]->icon_hands, ready_icon_slideshow);
    find_one_hand(ports[i], 1);
    if (ocurrent->beep)
      XBell(ports[i]->display, 0);
    XFlush(ports[i]->display);
  }
  loopmaster(0, ready_x_loop);
}
