/* Copyright (c) 1993 Sanjay Ghemawat */
/*
 * DateSet tests.
 *
 * Conversion tests
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#include "Month.h"
#include "WeekDay.h"
#include "dateset.h"
#include "lexer.h"

/* XXX - This is a hack */
extern "C" {
    extern long random();
}

static void t_empty();
static void t_single();
static void t_days();
static void t_months();
static void t_old();
static void t_convert();
static void t_io();

int
main() {
    t_empty();
    t_single();
    t_days();
    t_months();
    t_old();
    t_convert();
    t_io();
    return 0;
}

/* Compare contents of datesets. */
static void compare(DateSet const& d1, DateSet const& d2) {
    assert(d1.repeats() == d2.repeats());
    assert(d1.empty() == d2.empty());

    if (d1.empty()) {
	return;
    }

    Date x1, x2;
    d1.first(x1);
    d2.first(x2);

    /* Check at most 100 occurrences */
    for (int count = 0; count < 100; count++) {
	assert(x1 == x2);

	int result = d1.next(x1, x1);
	assert(result == d2.next(x2, x2));
	if (! result) {
	    break;
	}
    }
}

/* Perform IO checks. */
static void io_check1 (DateSet const& d) {
    FILE* out = fopen("dstest.tmp", "w");
    assert(out != 0);
    d.write(out);
    fprintf(out, "\n");
    fclose(out);

    Lexer lex = "dstest.tmp";
    DateSet d2;
    assert(d2.read(&lex));

    compare(d, d2);
}

static void io_check(DateSet const& d) {
    DateSet d2 = d;

    io_check1(d2);

    d2.set_start(Date::First()+1000);
    io_check1(d2);

    d2.set_finish(Date::Last()-1000);
    io_check1(d2);
}

/* Return number of months difference between d1 and d2 */
static int months_diff(Date d1, Date d2) {
    int day1, day2;
    Month m1, m2;
    int y1, y2;
    WeekDay w1, w2;

    d1.BreakDown(day1, w1, m1, y1);
    d2.BreakDown(day2, w2, m2, y2);

    return ((y1 - y2) * 12) + (m1 - m2);
}

// Test empty sets
static void t_empty() {
    DateSet d;
    Date tmp;

    printf("test empty\n");

    assert(d.empty());
    assert(! d.repeats());
    assert(! d.first(tmp));
    assert(! d.next(Date::First(), tmp));
    assert(d.type() == DateSet::Other);

    /* Delete test */
    {
	DateSet d2;

	d2 = d;
	d2.delete_occurrence(Date(1, Month::January(), 1980));
	compare(d, d2);
    }
}

// Test singleton sets
static void t_single() {
    DateSet empty;
    Date date(1, Month::January(), 1980);
    Date tmp;

    printf("test single\n");

    for (int i = 0; i < 100; i++) {
	DateSet d;
	d.set_date(date);

	assert(d.contains(date));
	assert(! d.repeats());
	assert(! d.empty());
	assert(d.first(tmp) && (tmp == date));
	assert(! d.next(date, tmp));
	assert(d.type() == DateSet::None);

	DateSet d2 = d;
	compare(d2, d);
	d2.set_start(date);
	compare(d2, d);
	d2.set_start(date+1);
	compare(d2, empty);

	d2 = d;
	compare(d2, d);
	d2.set_finish(date);
	compare(d2, d);
	d2.set_finish(date-1);
	compare(d2,empty);

	d2 = d;
	d2.delete_occurrence(date-1);
	compare(d2, d);
	d2.delete_occurrence(date+1);
	compare(d2, d);
	d2.delete_occurrence(date);
	compare(d2, empty);

	d2 = d;
	d2.delete_occurrence(date);
	compare(d2, empty);

	date += 1;
    }
}

static void t_days() {
    DateSet empty;
    Date init(1, Month::January(), 1980);
    Date tmp;

    printf("test day based repeat\n");
    for (int interval = 1; interval <= 10; interval++) {
	printf("   interval = %d\n", interval);
	for (int i = 0; i < 200; i += 17) {
	    /* Test for a number of anchors */
	    Date date = init + i;

	    DateSet d, d2, d3;
	    d.set_day_based_repeat(interval, date);

	    assert(d.contains(date));
	    assert(d.repeats());
	    assert(! d.empty());
	    switch(interval) {
	      case 1:
		assert(d.type() == DateSet::Daily);
		break;
	      case 7:
		assert(d.type() == DateSet::Weekly);
		break;
	      case 14:
		assert(d.type() == DateSet::BiWeekly);
		break;
	      case 21:
		assert(d.type() == DateSet::ThreeWeekly);
		break;
	      case 28:
		assert(d.type() == DateSet::FourWeekly);
		break;
	      default:
		assert(d.type() == DateSet::Other);
		break;
	    }

	    assert(d.first(tmp) && (((date - tmp) % interval) == 0));
	    assert(d.next(tmp, tmp) && (((date - tmp) % interval) == 0));
	    assert(d.next(tmp, tmp) && (((date - tmp) % interval) == 0));
	    assert(d.next(tmp, tmp) && (((date - tmp) % interval) == 0));
	    assert(d.next(tmp, tmp) && (((date - tmp) % interval) == 0));

	    d2.set_day_based_repeat(interval, date+interval);
	    compare(d, d2);

	    d2.set_day_based_repeat(interval, date-interval);
	    compare(d, d2);

	    /* Search starts on or after anchor */
	    for (int j = 0; j < interval; j++) {
		Date start = date + j;
		assert(d.next(start, tmp) && (tmp == (date + interval)));
	    }
	    assert(d.next(date+interval, tmp) && (tmp == (date + 2*interval)));

	    /* Search starts before anchor */
	    for (j = 1; j <= interval; j++) {
		Date start = date - j;
		assert(d.next(start, tmp) && (tmp == date));
	    }

	    /* Range checking */
	    for (int start = -4; start <= 4; start++) {
		for (int finish = -4; finish <= 4; finish++) {
		    d2 = d;
		    d2.set_start(date + start);
		    d2.set_finish(date + finish);

		    /* Delete test */
		    d3 = d2;
		    d3.delete_occurrence(date+start-1);
		    compare(d2, d3);
		    d3.delete_occurrence(date+finish+1);
		    compare(d2, d3);
		    d3.delete_occurrence(date);
		    assert(! d3.contains(date));
		    assert(!d3.next(date-1,tmp) || (tmp != date));

		    tmp = Date::First();
		    while (d2.next(tmp, tmp)) {
			assert(tmp >= date + start);
			assert(tmp <= date + finish);

			if (tmp < date) {
			    assert(((date - tmp) % interval) == 0);
			}
			else {
			    assert(((tmp - date) % interval) == 0);
			}
		    }
		}
	    }
	}
    }
}

static void t_months() {
    DateSet empty;
    Date init(1, Month::January(), 1980);

    printf("test month based repeat\n");


    for (int interval = 1; interval <= 20; interval++) {
	printf("   interval = %d\n", interval);
	for (int mday = 1; mday <= 28; mday += 7) {
	    Date date = Date(mday, Month::January(), 1980);

	    Date tmp;
	    DateSet d, d2, d3;
	    d.set_month_based_repeat(interval, date);

	    assert(d.contains(date));
	    assert(d.repeats());
	    assert(! d.empty());
	    switch (interval) {
	      case 1:
		assert(d.type() == DateSet::Monthly);
		break;
	      case 2:
		assert(d.type() == DateSet::TwoMonthly);
		break;
	      case 3:
		assert(d.type() == DateSet::ThreeMonthly);
		break;
	      case 4:
		assert(d.type() == DateSet::FourMonthly);
		break;
	      case 6:
		assert(d.type() == DateSet::SixMonthly);
		break;
	      case 12:
		assert(d.type() == DateSet::Annual);
		break;
	      default:
		assert(d.type() == DateSet::Other);
		break;
	    }

	    assert(d.first(tmp) && ((months_diff(date, tmp) % interval) == 0));
	    assert(d.next(tmp, tmp) && ((months_diff(date,tmp)%interval)==0));
	    assert(d.next(tmp, tmp) && ((months_diff(date,tmp)%interval)==0));
	    assert(d.next(tmp, tmp) && ((months_diff(date,tmp)%interval)==0));
	    assert(d.next(tmp, tmp) && ((months_diff(date,tmp)%interval)==0));
	    assert(d.next(tmp, tmp) && ((months_diff(date,tmp)%interval)==0));

	    assert(d.next(date,tmp) && ((months_diff(date,tmp)%interval)==0));
	    assert(d.next(tmp, tmp) && ((months_diff(date,tmp)%interval)==0));
	    assert(d.next(tmp, tmp) && ((months_diff(date,tmp)%interval)==0));
	    assert(d.next(tmp, tmp) && ((months_diff(date,tmp)%interval)==0));
	    assert(d.next(tmp, tmp) && ((months_diff(date,tmp)%interval)==0));

	    d.first(tmp);
	    d2.set_month_based_repeat(interval, tmp);
	    compare(d, d2);

	    d.next(date, tmp);
	    d2.set_month_based_repeat(interval, tmp);
	    compare(d, d2);

	    /* Search starts on or after anchor */
	    for (int j = 0; j < interval; j++) {
		Date start = date + j*31;
		assert(d.next(start, tmp) && ((months_diff(date,tmp)%interval) == 0));
	    }

	    /* Search starts before anchor */
	    for (j = 1; j <= interval; j++) {
		Date start = date - j;
		assert(d.next(start, tmp) && ((months_diff(tmp,date)%interval) == 0));
	    }

	    /* Range checking */
	    for (int start = -4; start <= 4; start++) {
		for (int finish = -4; finish <= 4; finish++) {
		    d2 = d;
		    d2.set_start(date + start);
		    d2.set_finish(date + finish);

		    /* Delete test */
		    d3 = d2;
		    d3.delete_occurrence(date+start-1);
		    compare(d2, d3);
		    d3.delete_occurrence(date+finish+1);
		    compare(d2, d3);
		    d3.delete_occurrence(date);
		    assert(! d3.contains(date));
		    assert(!d3.next(date-1,tmp) || (tmp != date));

		    tmp = Date::First();
		    while (d2.next(tmp, tmp)) {
			assert(tmp >= date + start);
			assert(tmp <= date + finish);

			if (tmp < date) {
			    assert((months_diff(date,tmp) % interval) == 0);
			}
			else {
			    assert((months_diff(tmp,date) % interval) == 0);
			}
		    }
		}
	    }
	}
    }

    /* Check month size idiosyncracies */
    {
	DateSet d;
	Date tmp;
	Month feb = Month::February();

	d.set_month_based_repeat(12, Date(29, feb, 1980));

	assert(d.next(init, tmp) && (tmp == Date(29, feb, 1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, feb, 1984)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, feb, 1988)));

	d.set_month_based_repeat(1, Date(29, feb, 1980));

	assert(d.next(init, tmp) && (tmp == Date(29, Month::January(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, Month::February(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, Month::March(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, Month::April(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, Month::May(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, Month::June(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, Month::July(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, Month::August(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, Month::September(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, Month::October(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, Month::November(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, Month::December(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, Month::January(),1981)));
	assert(d.next(tmp, tmp) && (tmp == Date(29, Month::March(),1981)));

	d.set_month_based_repeat(1, Date(31, Month::January(), 1980));

	assert(d.next(init, tmp) && (tmp == Date(31, Month::January(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::March(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::May(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::July(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::August(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::October(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::December(),1980)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::January(),1981)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::March(),1981)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::May(),1981)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::July(),1981)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::August(),1981)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::October(),1981)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::December(),1981)));
	assert(d.next(tmp, tmp) && (tmp == Date(31, Month::January(),1982)));
    }
}

static void t_io() {
    Date date(1, Month::January(), 1980);
    DateSet d;
    int i, j, k;

    printf("test io\n");

    d.set_empty();
    io_check(d);

    d.set_date(date);
    io_check(d);

    for (i = 1; i < 4; i++) {
	d.set_day_based_repeat(i, date);
	io_check(d);

	DateSet d2;

	d2 = d;
	d2.set_start(date-1);
	io_check(d2);

	d2 = d;
	d2.set_finish(date+1);
	io_check(d2);

	for (j = -2; j <= 2; j++) {
	    for (k = -2; k <= 2; k++) {
		d2 = d;
		d2.set_start(date-k);
		d2.set_finish(date+k);
		io_check(d2);
	    }
	}
    }

    for (i = 1; i < 4; i++) {
	d.set_month_based_repeat(i, date);
	io_check(d);

	DateSet d2;

	d2 = d;
	d2.set_start(date-1);
	io_check(d2);

	d2 = d;
	d2.set_finish(date+1);
	io_check(d2);

	for (j = -2; j <= 2; j++) {
	    for (k = -2; k <= 2; k++) {
		d2 = d;
		d2.set_start(date-k);
		d2.set_finish(date+k);
		io_check(d2);
	    }
	}
    }
}

/*
 * requires	0 <= min <= max <= SISetLargestMember
 * modifies	set
 * effects	post(set) = { x | (min <= x) and (x <= max) with 50% prob. }
 */
static void randomize(SmallIntSet& set, int min, int max) {
    unsigned long int value = random();

    set.Clear();
    for (unsigned int i = min; i <= max; i++) {
	if (value & (1 << i)) {
	    set.Insert(i);
	}
    }
}

static void old_mcheck(DateSet const& d,
		       SmallIntSet days,
		       SmallIntSet months,
		       Date startDate,
		       Date firstDate,
		       Date lastDate) {
    int count = 0;
    Date tmp = startDate;
    while (d.next(tmp, tmp)) {
	int mday, year;
	Month month;
	WeekDay wday;
	
	assert((tmp >= firstDate) && (tmp <= lastDate));
	assert(d.contains(tmp));
	tmp.BreakDown(mday, wday, month, year);
	assert(days.Member(mday) && months.Member(month.Index()));

	Date tmp2;
	DateSet d2 = d;

	d2.delete_occurrence(tmp-1);
	assert(d2.contains(tmp));
	assert(d2.next(tmp-1,tmp2) && (tmp2 == tmp));
	d2.delete_occurrence(tmp+1);
	assert(d2.contains(tmp));
	assert(d2.next(tmp-1,tmp2) && (tmp2 == tmp));
	d2.delete_occurrence(tmp);
	assert(!d2.contains(tmp));
	assert(!d2.next(tmp-1,tmp2) || (tmp2 != tmp));
	
	count++;
	if (count > 20) {
	    break;
	}
    }
}

static void old_wcheck(DateSet const& d,
		       SmallIntSet days,
		       SmallIntSet months,
		       Date startDate,
		       Date firstDate,
		       Date lastDate) {
    int count = 0;
    Date tmp = startDate;
    while (d.next(tmp, tmp)) {
	int mday, year;
	Month month;
	WeekDay wday;
	
	assert((tmp >= firstDate) && (tmp <= lastDate));
	assert(d.contains(tmp));
	tmp.BreakDown(mday, wday, month, year);
	assert(days.Member(wday.Index()) && months.Member(month.Index()));

	Date tmp2;
	DateSet d2 = d;

	d2.delete_occurrence(tmp-1);
	assert(d2.contains(tmp));
	assert(d2.next(tmp-1,tmp2) && (tmp2 == tmp));
	d2.delete_occurrence(tmp+1);
	assert(d2.contains(tmp));
	assert(d2.next(tmp-1,tmp2) && (tmp2 == tmp));
	d2.delete_occurrence(tmp);
	assert(!d2.contains(tmp));
	assert(!d2.next(tmp-1,tmp2) || (tmp2 != tmp));
	
	count++;
	if (count > 20) {
	    break;
	}
    }
}

static void t_old() {
    DateSet d;
    Date firstDate = Date::First();
    Date lastDate = Date::Last();
    Date startDate = Date(1, Month::January(), 1980);
    Date finishDate = Date(31, Month::December(), 1982);

    printf("test old format\n");

    /* Test monthly repetition */
    for (int i = 0; i < 100; i++) {
	SmallIntSet days, months;
	randomize(days, 1, 31);
	randomize(months, 1, 12);

	d.set_month_set(days, months);

	old_mcheck(d, days, months, firstDate, firstDate, lastDate);
	if ((i % 29) == 0) io_check(d);

	DateSet d2 = d;
	d2.delete_occurrence(startDate);
	if ((i % 29) == 0) io_check(d2);
	d2.delete_occurrence(finishDate);
	if ((i % 29) == 0) io_check(d2);

	d.set_start(startDate);
	d.set_finish(finishDate);
	old_mcheck(d, days, months, startDate - 20, startDate, finishDate);
	if ((i % 29) == 0) io_check(d2);
    }

    /* Test weekly repetition */
    for (i = 0; i < 100; i++) {
	SmallIntSet days, months;
	randomize(days, 1, 7);
	randomize(months, 1, 12);

	d.set_week_set(days, months);

	old_wcheck(d, days, months, firstDate, firstDate, lastDate);
	if ((i % 29) == 0) io_check(d);

	DateSet d2 = d;
	d2.delete_occurrence(startDate);
	if ((i % 29) == 0) io_check(d2);
	d2.delete_occurrence(finishDate);
	if ((i % 29) == 0) io_check(d2);

	d.set_start(startDate);
	d.set_finish(finishDate);
	old_wcheck(d, days, months, startDate - 20, startDate, finishDate);
	if ((i % 29) == 0) io_check(d);
    }
}

static void t_convert() {
    SmallIntSet emptySet;
    SmallIntSet singleSet;
    SmallIntSet allMonths;
    SmallIntSet rangeSet;
    SmallIntSet allWeekDays;
    SmallIntSet allMonthDays;
    DateSet d;
    Date tmp;
    int i;

    printf("test conversion\n");

    emptySet.Clear();
    singleSet.Clear();
    singleSet.Insert(5);

    allMonths.Clear();
    for (i = 1; i <= 12; i++) {
	allMonths.Insert(i);
    }

    rangeSet.Clear();
    for (i = 3; i <= 6; i++) {
	rangeSet.Insert(i);
    }

    allWeekDays.Clear();
    for (i = 1; i <= 7; i++) {
	allWeekDays.Insert(i);
    }

    allMonthDays.Clear();
    for (i = 1; i <= 31; i++) {
	allMonthDays.Insert(i);
    }

    {
	d.set_month_set(emptySet, singleSet);
	assert(d.empty());

	d.set_week_set(emptySet, singleSet);
	assert(d.empty());

	d.set_month_set(singleSet, singleSet);
	d.set_start(Date(1, Month::January(), 1980));
	d.set_finish(Date(31, Month::December(), 1979));
	assert(d.empty());
    }

    {
	d.set_week_set(singleSet, allMonths);
	assert(d.type() == DateSet::Weekly);

	d.set_week_set(singleSet, allMonths);
	d.set_start(Date(1, Month::January(), 1980));
	d.set_finish(Date(31, Month::December(), 1990));
	assert(d.type() == DateSet::Weekly);

	assert(d.first(tmp) && (tmp >= Date(1, Month::January(), 1980)));
    }

    {
	d.set_week_set(singleSet, rangeSet);
	d.set_start(Date(1, Month::January(), 1980));
	d.set_finish(Date(31, Month::December(), 1980));
	assert(d.type() == DateSet::Weekly);

	assert(d.first(tmp) && (tmp >= Date(1, Month::January(), 1980)));
    }

    {
	d.set_week_set(allWeekDays, allMonths);
	d.set_start(Date(1, Month::January(), 1980));
	d.set_finish(Date(31, Month::December(), 1980));
	assert(d.type() == DateSet::Daily);

	assert(d.first(tmp) && (tmp >= Date(1, Month::January(), 1980)));

	d.set_week_set(allWeekDays, rangeSet);
	d.set_start(Date(1, Month::January(), 1980));
	d.set_finish(Date(31, Month::December(), 1980));
	assert(d.type() == DateSet::Daily);

	assert(d.first(tmp) && (tmp >= Date(1, Month::January(), 1980)));
    }

    {
	d.set_month_set(singleSet, singleSet);
	assert(d.type() == DateSet::Annual);
    }

    {
	d.set_month_set(singleSet, allMonths);
	assert(d.type() == DateSet::Monthly);
    }

    {
	d.set_month_set(singleSet, rangeSet);
	d.set_start(Date(1, Month::January(), 1980));
	d.set_finish(Date(31, Month::December(), 1980));
	assert(d.type() == DateSet::Monthly);
    }

    {
	d.set_month_set(allMonthDays, allMonths);
	assert(d.type() == DateSet::Daily);
    }

    {
	d.set_month_set(allMonthDays, rangeSet);
	d.set_start(Date(1, Month::January(), 1980));
	d.set_finish(Date(31, Month::December(), 1980));
	assert(d.type() == DateSet::Daily);
    }

    {
	d.set_month_set(rangeSet, singleSet);
	d.set_start(Date(1, Month::January(), 1980));
	d.set_finish(Date(31, Month::December(), 1980));
	assert(d.type() == DateSet::Daily);
    }
}
