/*
 * This file is part of the Sofia-SIP package
 *
 * Copyright (C) 2005 Nokia Corporation.
 *
 * Contact: Pekka Pessi <pekka.pessi@nokia.com>
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA
 *
 */

/**@ingroup su_root_ex
 * @CFILE torture_su_timer.c
 *
 * @brief Test program for su timers
 *
 * @author Pekka Pessi <Pekka.Pessi@nokia.com>
 *
 * @internal
 *
 * @date Created: Fri Oct 19 08:53:55 2001 pessi
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include <assert.h>

struct tester;

#define SU_ROOT_MAGIC_T struct tester
#define SU_INTERNAL_P   su_root_t *
#define SU_TIMER_ARG_T  struct timing

#include "sofia-sip/su.h"
#include "sofia-sip/su_wait.h"
#include "sofia-sip/su_log.h"

struct timing
{
  int       t_run;
  int       t_times;
  su_time_t t_prev;
};

struct tester
{
  su_root_t *root;
  su_timer_t *t, *t1;
  unsigned times;
  void *sentinel;
};

void
print_stamp(struct tester *x, su_timer_t *t, struct timing *ti)
{
  su_time_t now = su_now(), prev = ti->t_prev;

  ti->t_prev = now;

  if (!ti->t_run)
    su_timer_set(t, print_stamp, ti);

  printf("timer interval %f\n", 1000 * su_time_diff(now, prev));

  if (++ti->t_times >= 10)
    su_timer_reset(t);
}

void
print_X(struct tester *x, su_timer_t *t1, struct timing *ti)
{
  su_timer_set(t1, print_X, ti);
  putchar('X'); fflush(stdout);
}

#if HAVE_SIGNAL

#include <signal.h>

su_msg_r intr_msg = SU_MSG_R_INIT;

static RETSIGTYPE intr_handler(int signum)
{
  su_msg_send(intr_msg);
}

static void test_break(struct tester *tester, su_msg_r msg, su_msg_arg_t *arg)
{
  su_root_break(tester->root);
}

#endif

void
end_test(struct tester *tester, su_timer_t *t, struct timing *ti)
{
  su_timer_destroy(t);
  su_timer_reset(tester->t);
  su_timer_reset(tester->t1);
  su_root_break(tester->root);
}

void
increment(struct tester *tester, su_timer_t *t, struct timing *ti)
{
  tester->times++;

  if ((void *)ti == (void*)tester->sentinel)
    su_root_break(tester->root);
}

void
usage(char const *name)
{
  fprintf(stderr, "usage: %s [-1r] [-Nnum] [interval]\n", name);
  exit(1);
}

/*
 * test su_timer functionality:
 *
 * Create a timer, executing print_stamp() in every 20 ms
 */
int main(int argc, char *argv[])
{
  su_root_t *root;
  su_timer_t *t, *t1, *t_end;
  su_timer_t **timers;
  su_duration_t interval = 60;
  char *argv0 = argv[0];
  char *s;
  int use_t1 = 0;
  su_time_t now, started, inserted;
  unsigned i, N = 500;

  struct timing timing[1] = {{ 0 }};
  struct tester tester[1] = {{ 0 }};

  while (argv[1] && argv[1][0] == '-') {
    char *o = argv[1] + 1;
    while (*o) {
      if (*o == '1')
	o++, use_t1 = 1;
      else if (*o == 'r')
	o++, timing->t_run = 1;
      else if (*o == 'N') {
	if (o[1])
	  N = strtoul(o + 1, &o, 0);
	else if (argv[2])
	  N = strtoul(argv++[2], &o, 0);
	break;
      }
      else
	break;

    }
    if (*o)
      usage(argv0);
    argv++;
  }

  if (argv[1]) {
    interval = strtoul(argv[1], &s, 10);

    if (interval == 0 || s == argv[1])
      usage(argv0);
  }

  su_init(); atexit(su_deinit);

  tester->root = root = su_root_create(tester);

#if HAVE_SIGNAL
  su_msg_create(intr_msg,
		su_root_task(root),
		su_root_task(root),
		test_break, 0);

  signal(SIGINT, intr_handler);
#if HAVE_SIGPIPE
  signal(SIGQUIT, intr_handler);
  signal(SIGHUP, intr_handler);
#endif
#endif

  t = su_timer_create(su_root_task(root), interval);
  t1 = su_timer_create(su_root_task(root), 1);
  t_end = su_timer_create(su_root_task(root), 20 * interval);

  if (t == NULL || t1 == NULL || t_end == NULL)
    su_perror("su_timer_create"), exit(1);

  tester->t = t, tester->t1 = t1;

  timing->t_prev = su_now();

  if (timing->t_run)
    su_timer_run(t, print_stamp, timing);
  else
    su_timer_set(t, print_stamp, timing);

  if (use_t1)
    su_timer_set(t1, print_X, NULL);

  su_timer_set(t_end, end_test, NULL);

  su_root_run(root);

#if HAVE_SIGNAL
  su_msg_destroy(intr_msg);
#endif

  su_timer_destroy(t);
  su_timer_destroy(t1);

  if (timing->t_times != 10) {
    fprintf(stderr, "%s: t expired %d times (expecting 10)\n",
	    argv0, timing->t_times);
    return 1;
  }

  /* Insert timers in order */
  timers = calloc(N, sizeof *timers);
  if (!timers) { perror("calloc"); exit(1); }

  for (i = 0; i < N; i++) {
    t = su_timer_create(su_root_task(root), 1000);
    if (!t) { perror("su_timer_create"); exit(1); }
    timers[i] = t;
  }

  now = started = su_now();

  for (i = 0; i < N; i++) {
    if (++now.tv_usec == 0) ++now.tv_sec;
    su_timer_set_at(timers[i], increment, (void *)(intptr_t)i, now);
  }

  tester->sentinel = (void*)(intptr_t)(i - 1);

  inserted = su_now();

  su_root_run(root);

  now = su_now();

  printf("Inserting %u timers took %f millisec\n",
	 i, su_time_diff(inserted, started) * 1000);

  printf("Processing %u/%u timers took %f millisec (%f expected)\n",
	 tester->times, i,
	 su_time_diff(now, started) * 1000,
	 (double)i / 1000);

  for (i = 0; i < N; i++) {
    su_timer_destroy(timers[i]);
  }

  su_root_destroy(root);

  su_deinit();

  return 0;
}