/*
 * Copyright (c) 2010-2011 Apple Inc. All rights reserved.
 *
 * @APPLE_APACHE_LICENSE_HEADER_START@
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @APPLE_APACHE_LICENSE_HEADER_END@
 */

#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#ifdef __APPLE__
#include <mach/mach_time.h>
#include <libkern/OSAtomic.h>
#endif

#include <dispatch/dispatch.h>

#include <bsdtests.h>
#include "dispatch_test.h"

#define delay (1ull * NSEC_PER_SEC)
#define interval (5ull * NSEC_PER_USEC)

#define N 25000

static dispatch_source_t t[N];
static dispatch_queue_t q;
static dispatch_group_t g;

static volatile int32_t count;
static mach_timebase_info_data_t tbi;
static uint64_t start, last;

#define elapsed_ms(x) (((now-(x))*tbi.numer/tbi.denom)/(1000ull*NSEC_PER_USEC))

static
void
test_fin(void *cxt)
{
	uint32_t finalCount = (uint32_t)count;
	fprintf(stderr, "Called back every %llu us on average\n",
			(delay/finalCount)/NSEC_PER_USEC);
	test_long_less_than("Frequency", 1,
		(long)ceil((double)delay/(double)(finalCount*interval)));
	int i;
	for (i = 0; i < N; i++) {
		dispatch_source_cancel(t[i]);
		dispatch_release(t[i]);
	}
	dispatch_resume(q);
	dispatch_release(q);
	dispatch_release(g);
	test_ptr("finalizer ran", cxt, cxt);
	test_stop();
}

static
void
test_short_timer(void)
{
	// Add a large number of timers with suspended target queue in front of
	// the timer being measured <rdar://problem/7401353>
	g = dispatch_group_create();
	q = dispatch_queue_create("q", NULL);
	int i;
	for (i = 0; i < N; i++) {
		t[i] = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, q);
		dispatch_source_set_timer(t[i], DISPATCH_TIME_NOW, interval, 0);
		dispatch_group_enter(g);
		dispatch_source_set_registration_handler(t[i], ^{
			dispatch_suspend(t[i]);
			dispatch_group_leave(g);
		});
		dispatch_resume(t[i]);
	}
	// Wait for registration & configuration of all timers
	dispatch_group_wait(g, DISPATCH_TIME_FOREVER);
	dispatch_suspend(q);
	for (i = 0; i < N; i++) {
		dispatch_resume(t[i]);
	}

	dispatch_source_t s = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,
			0, 0, dispatch_get_global_queue(0, 0));
	test_ptr_notnull("dispatch_source_create", s);
	dispatch_source_set_timer(s, DISPATCH_TIME_NOW, interval, 0);
	dispatch_source_set_event_handler(s, ^{
		uint64_t now = mach_absolute_time();
		if (!count) {
			dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay),
					dispatch_get_global_queue(0, 0), ^{
				dispatch_source_cancel(s);
				dispatch_release(s);
			});
			fprintf(stderr, "First timer callback  (after %4llu ms)\n",
					elapsed_ms(start));
		}
		OSAtomicIncrement32(&count);
		if (elapsed_ms(last) >= 100) {
			fprintf(stderr, "%5d timer callbacks (after %4llu ms)\n", count,
					elapsed_ms(start));
			last = now;
		}
	});
	dispatch_set_context(s, s);
	dispatch_set_finalizer_f(s, test_fin);
	fprintf(stderr, "Scheduling %llu us timer\n", interval/NSEC_PER_USEC);
	start = last = mach_absolute_time();
	dispatch_resume(s);
}

int
main(void)
{
	dispatch_test_start("Dispatch Short Timer"); // <rdar://problem/7765184>
	mach_timebase_info(&tbi);
	test_short_timer();
	dispatch_main();
	return 0;
}
