/* given a preferred time, get the next valid time within a time period */
void get_next_valid_time(time_t pref_time, time_t *valid_time, timeperiod *tperiod)
{
	time_t current_time = (time_t)0L;

	/* get time right now, preferred time must be now or in the future */
	time(&current_time);

	pref_time = (pref_time < current_time) ? current_time : pref_time;

	_get_next_valid_time(pref_time, valid_time, tperiod);
}
int main(int argc, char **argv) {
	int result;
	int error = FALSE;
	char *buffer = NULL;
	int display_license = FALSE;
	int display_help = FALSE;
	int c = 0;
	struct tm *tm;
	time_t current_time;
	time_t test_time;
	time_t saved_test_time;
	time_t next_valid_time = 0L;
	time_t chosen_valid_time = 0L;
	char datestring[256];
	host *temp_host = NULL;
	hostgroup *temp_hostgroup = NULL;
	hostsmember *temp_member = NULL;
	timeperiod *temp_timeperiod = NULL;
	int is_valid_time = 0;
	int iterations = 1000;

	plan_tests(6043);

	/* reset program variables */
	reset_variables();

	printf("Reading configuration data...\n");

	config_file = strdup("smallconfig/icinga.cfg");
	/* read in the configuration files (main config file, resource and object config files) */
	result = read_main_config_file(config_file);
	ok(result == OK, "Read main configuration file okay - if fails, use icinga -v to check");

	result = read_all_object_data(config_file);
	ok(result == OK, "Read all object config files");

	result = pre_flight_check();
	ok(result == OK, "Preflight check okay");

	time(&current_time);
	test_time = current_time;
	saved_test_time = current_time;

	temp_timeperiod = find_timeperiod("none");

	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == ERROR, "No valid time because time period is empty");

	get_next_valid_time(current_time, &next_valid_time, temp_timeperiod);
	ok((next_valid_time - current_time) <= 2, "Next valid time should be the current_time, but with a 2 second tolerance");

	temp_timeperiod = find_timeperiod("24x7");

	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == OK, "Fine because 24x7");

	get_next_valid_time(current_time, &next_valid_time, temp_timeperiod);
	ok(current_time == next_valid_time, "Current time should be the next valid time");

	/* 2009-10-25 is the day when clocks go back an hour in Europe. Bug happens during 23:00 to 00:00 */
	/* This is 23:01:01 */
	saved_test_time = 1256511661;
	saved_test_time = saved_test_time - (24 * 60 * 60);

	putenv("TZ=UTC");
	tzset();
	test_time = saved_test_time;
	c = 0;
	while (c < iterations) {
		is_valid_time = check_time_against_period(test_time, temp_timeperiod);
		ok(is_valid_time == OK, "Always OK for 24x7 with TZ=UTC");
		chosen_valid_time = 0L;
		_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
		ok(test_time == chosen_valid_time, "get_next_valid_time always returns same time");
		test_time += 1800;
		c++;
	}

	putenv("TZ=Europe/London");
	tzset();
	test_time = saved_test_time;
	c = 0;
	while (c < iterations) {
		is_valid_time = check_time_against_period(test_time, temp_timeperiod);
		ok(is_valid_time == OK, "Always OK for 24x7 with TZ=Europe/London");
		_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
		ok(test_time == chosen_valid_time, "get_next_valid_time always returns same time, time_t=%lu", test_time);
		test_time += 1800;
		c++;
	}

	/* 2009-11-01 is the day when clocks go back an hour in America. Bug happens during 23:00 to 00:00 */
	/* This is 23:01:01 */
	saved_test_time = 1256511661;
	saved_test_time = saved_test_time - (24 * 60 * 60);

	putenv("TZ=America/New_York");
	tzset();
	test_time = saved_test_time;
	c = 0;
	while (c < iterations) {
		is_valid_time = check_time_against_period(test_time, temp_timeperiod);
		ok(is_valid_time == OK, "Always OK for 24x7 with TZ=America/New_York");
		_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
		ok(test_time == chosen_valid_time, "get_next_valid_time always returns same time, time_t=%lu", test_time);
		test_time += 1800;
		c++;
	}

	/* Tests around clock change going back for TZ=Europe/London. 1256511661 = Sun Oct
	 25 23:01:01 2009 */
	/* A little trip to Paris*/
	putenv("TZ=Europe/Paris");
	tzset();


	/* Timeperiod exclude tests, from Jean Gabes */
	temp_timeperiod = find_timeperiod("Test_exclude");
	ok(temp_timeperiod != NULL, "ME: Testing Exclude timeperiod");
	test_time = 1278939600;
	/* printf("Testing at time %s", ctime(&test_time)); */
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == ERROR, "ME: 12 Jul 2010 15:00:00 - false");

	_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	/* printf("JEAN: Got chosent time at %s", ctime(&chosen_valid_time)); */
	todo_start("Bug in exclude");
	ok(chosen_valid_time == 1288103400, "ME: Next valid time=Tue Oct 26 16:30:00 2010");
	todo_end();


	temp_timeperiod = find_timeperiod("Test_exclude2");
	ok(temp_timeperiod != NULL, "ME: Testing Exclude timeperiod 2");
	test_time = 1278939600;
	/* printf("Testing at time %s", ctime(&test_time)); */
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == ERROR, "ME: 12 Jul 2010 15:00:00 - false");
	_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	/* printf("JEAN: Got chosent time at %s", ctime(&chosen_valid_time)); */
	todo_start("Bug in exclude 2");
	ok(chosen_valid_time == 1279058340, "ME: Next valid time=Tue Jul 13 23:59:00 2010");
	todo_end();


	temp_timeperiod = find_timeperiod("Test_exclude3");
	ok(temp_timeperiod != NULL, "ME: Testing Exclude timeperiod 3");
	test_time = 1278939600;
	/* printf("Testing at time %s", ctime(&test_time)); */
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == ERROR, "ME: 12 Jul 2010 15:00:00 - false");
	_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	/* printf("JEAN: Got chosent time at %s", ctime(&chosen_valid_time)); */
	todo_start("Bug in exclude 3");
	ok(chosen_valid_time == 1284474600, "ME: Next valid time=Tue Sep 14 16:30:00 2010");
	todo_end();


	temp_timeperiod = find_timeperiod("Test_exclude4");
	ok(temp_timeperiod != NULL, "ME: Testing Exclude timeperiod 4");
	test_time = 1278939600;
	/* printf("Testing at time %s", ctime(&test_time)); */
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == ERROR, "ME: 12 Jul 2010 15:00:00 - false");
	_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	/* printf("JEAN: Got chosent time at %s", ctime(&chosen_valid_time)); */
	todo_start("Bug in exclude 3");
	ok(chosen_valid_time == 1283265000, "ME: Next valid time=Tue Aug 31 16:30:00 2010");
	todo_end();




	/* Back to New york */
	putenv("TZ=America/New_York");
	tzset();




	temp_timeperiod = find_timeperiod("sunday_only");
	ok(temp_timeperiod != NULL, "Testing Sunday 00:00-01:15,03:15-22:00");
	putenv("TZ=Europe/London");
	tzset();

	test_time = 1256421000;
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == ERROR, "Sat Oct 24 22:50:00 2009 - false");
	_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	ok(chosen_valid_time == 1256425200, "Next valid time=Sun Oct 25 00:00:00 2009");

	test_time = 1256421661;
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == ERROR, "Sat Oct 24 23:01:01 2009 - false");
	_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	ok(chosen_valid_time == 1256425200, "Next valid time=Sun Oct 25 00:00:00 2009");

	test_time = 1256425400;
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == OK, "Sun Oct 25 00:03:20 2009 - true");
	_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	ok(chosen_valid_time == test_time, "Next valid time=Sun Oct 25 00:03:20 2009");

	test_time = 1256429700;
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == OK, "Sun Oct 25 01:15:00 2009 - true");
	_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	ok(chosen_valid_time == test_time, "Next valid time=Sun Oct 25 01:15:00 2009");

	test_time = 1256430400;
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == ERROR, "Sun Oct 25 01:26:40 2009 - false");
	_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	todo_start("Is a bug in get_next_valid_time for a time that falls in the DST change hour period");
	ok(chosen_valid_time == 1256440500, "Next valid time=Sun Oct 25 03:15:00 2009") || printf("chosen_valid_time=%lu\n", chosen_valid_time);
	todo_end();

	test_time = 1256440500;
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == OK, "Sun Oct 25 03:15:00 2009 - true");
	_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	ok(chosen_valid_time == test_time, "Next valid time=Sun Oct 25 03:15:00 2009");

	test_time = 1256500000;
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == OK, "Sun Oct 25 19:46:40 2009 - true");
	_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	ok(chosen_valid_time == 1256500000, "Next valid time=Sun Oct 25 19:46:40 2009");

	test_time = 1256508000;
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == OK, "Sun Oct 25 22:00:00 2009 - true");
	_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	ok(chosen_valid_time == 1256508000, "Next valid time=Sun Oct 25 22:00:00 2009");

	test_time = 1256508001;
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == ERROR, "Sun Oct 25 22:00:01 2009 - false");
	//_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	_get_next_valid_time_per_timeperiod(test_time, &chosen_valid_time, test_time, temp_timeperiod);
	ok(chosen_valid_time == 1257033600, "Next valid time=Sun Nov 1 00:00:00 2009");

	test_time = 1256513000;
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == ERROR, "Sun Oct 25 23:23:20 2009 - false");
	//_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	_get_next_valid_time_per_timeperiod(test_time, &chosen_valid_time, test_time, temp_timeperiod);
	ok(chosen_valid_time == 1257033600, "Next valid time=Sun Nov 1 00:00:00 2009");




	temp_timeperiod = find_timeperiod("weekly_complex");
	ok(temp_timeperiod != NULL, "Testing complex weekly timeperiod definition");
	putenv("TZ=America/New_York");
	tzset();

	test_time = 1268109420;
	is_valid_time = check_time_against_period(test_time, temp_timeperiod);
	ok(is_valid_time == ERROR, "Mon Mar  8 23:37:00 2010 - false");
	//_get_next_valid_time(test_time, test_time, &chosen_valid_time, temp_timeperiod);
	_get_next_valid_time_per_timeperiod(test_time, &chosen_valid_time, test_time, temp_timeperiod);
	ok(chosen_valid_time == 1268115300, "Next valid time=Tue Mar  9 01:15:00 2010");

	cleanup();

	my_free(config_file);

	return exit_status();
}
static void _get_next_invalid_time(time_t pref_time, time_t *invalid_time, timeperiod *tperiod)
{
	timeperiodexclusion *temp_timeperiodexclusion = NULL;
	int depth = 0;
	int max_depth = 300; // commonly roughly equal to "days in the future"
	struct tm *t, tm_s;
	time_t earliest_time = pref_time;
	time_t last_earliest_time = 0;
	time_t midnight = (time_t)0L;
	time_t day_range_start = (time_t)0L;
	time_t day_range_end = (time_t)0L;
	time_t potential_time = 0;
	time_t excluded_time = 0;
	time_t last_range_end = 0;
	int have_earliest_time = FALSE;
	timerange *last_range = NULL, *temp_timerange = NULL;

	/* if no period was specified, assume the time is good */
	if (tperiod == NULL || check_time_against_period(pref_time, tperiod) == ERROR) {
		*invalid_time = pref_time;
		return;
	}

	/* first excluded time may well be the time we're looking for */
	for (temp_timeperiodexclusion = tperiod->exclusions; temp_timeperiodexclusion != NULL; temp_timeperiodexclusion = temp_timeperiodexclusion->next) {
		/* if pref_time is excluded, we're done */
		if (check_time_against_period(pref_time, temp_timeperiodexclusion->timeperiod_ptr) != ERROR) {
			*invalid_time = pref_time;
			return;
		}
		_get_next_valid_time(pref_time, &potential_time, temp_timeperiodexclusion->timeperiod_ptr);
		if (!excluded_time || excluded_time > potential_time)
			excluded_time = potential_time;
	}

	while (earliest_time != last_earliest_time && depth < max_depth) {
		have_earliest_time = FALSE;
		depth++;
		last_earliest_time = earliest_time;

		t = localtime_r((time_t *)&earliest_time, &tm_s);
		t->tm_sec = 0;
		t->tm_min = 0;
		t->tm_hour = 0;
		midnight = mktime(t);

		temp_timerange = _get_matching_timerange(earliest_time, tperiod);

		for (; temp_timerange; last_range = temp_timerange, temp_timerange = temp_timerange->next) {
			/* ranges with start/end of zero mean exlude this day */

			day_range_start = (time_t)(midnight + temp_timerange->range_start);
			day_range_end = (time_t)(midnight + temp_timerange->range_end);

#ifdef TEST_TIMEPERIODS_B
			printf("  INVALID RANGE START: %lu (%lu) = %s", temp_timerange->range_start, (unsigned long)day_range_start, ctime(&day_range_start));
			printf("  INVALID RANGE END:   %lu (%lu) = %s", temp_timerange->range_end, (unsigned long)day_range_end, ctime(&day_range_end));
#endif

			if (temp_timerange->range_start == 0 && temp_timerange->range_end == 0)
				continue;

			if (excluded_time && day_range_end > excluded_time) {
				earliest_time = excluded_time;
				have_earliest_time = TRUE;
				break;
			}

			/*
			 * Unless two consecutive days have adjoining timeranges,
			 * the end of the last period is the start of the first
			 * invalid time. This only needs special-casing when the
			 * last range of the previous day ends at midnight, and
			 * also catches the special case when there are only
			 * exceptions in a timeperiod and some days are skipped
			 * entirely.
			 */
			if (last_range && last_range->range_end == SECS_PER_DAY && last_range_end && day_range_start != last_range_end) {
				earliest_time = last_range_end;
				have_earliest_time = TRUE;
				break;
			}

			/* stash this day_range_end in case we skip a day */
			last_range_end = day_range_end;

			if (pref_time <= day_range_end && temp_timerange->range_end != SECS_PER_DAY) {
				earliest_time = day_range_end;
				have_earliest_time = TRUE;
#ifdef TEST_TIMEPERIODS_B
				printf("    EARLIEST INVALID TIME: %lu = %s", (unsigned long)earliest_time, ctime(&earliest_time));
#endif
				break;
			}
		}

		/* if we found this in the exclusions, we're done */
		if (have_earliest_time == TRUE) {
			break;
		}

		earliest_time = midnight + SECS_PER_DAY;
	}
#ifdef TEST_TIMEPERIODS_B
	printf("    FINAL EARLIEST INVALID TIME: %lu = %s", (unsigned long)earliest_time, ctime(&earliest_time));
#endif

	if (depth == max_depth)
		*invalid_time = pref_time;
	else
		*invalid_time = earliest_time;
}