/*
 * Legal Notice
 *
 * This document and associated source code (the "Work") is a part of a
 * benchmark specification maintained by the TPC.
 *
 * The TPC reserves all right, title, and interest to the Work as provided
 * under U.S. and international laws, including without limitation all patent
 * and trademark rights therein.
 *
 * No Warranty
 *
 * 1.1 TO THE MAXIMUM EXTENT PERMITTED BY APPLICABLE LAW, THE INFORMATION
 *     CONTAINED HEREIN IS PROVIDED "AS IS" AND WITH ALL FAULTS, AND THE
 *     AUTHORS AND DEVELOPERS OF THE WORK HEREBY DISCLAIM ALL OTHER
 *     WARRANTIES AND CONDITIONS, EITHER EXPRESS, IMPLIED OR STATUTORY,
 *     INCLUDING, BUT NOT LIMITED TO, ANY (IF ANY) IMPLIED WARRANTIES,
 *     DUTIES OR CONDITIONS OF MERCHANTABILITY, OF FITNESS FOR A PARTICULAR
 *     PURPOSE, OF ACCURACY OR COMPLETENESS OF RESPONSES, OF RESULTS, OF
 *     WORKMANLIKE EFFORT, OF LACK OF VIRUSES, AND OF LACK OF NEGLIGENCE.
 *     ALSO, THERE IS NO WARRANTY OR CONDITION OF TITLE, QUIET ENJOYMENT,
 *     QUIET POSSESSION, CORRESPONDENCE TO DESCRIPTION OR NON-INFRINGEMENT
 *     WITH REGARD TO THE WORK.
 * 1.2 IN NO EVENT WILL ANY AUTHOR OR DEVELOPER OF THE WORK BE LIABLE TO
 *     ANY OTHER PARTY FOR ANY DAMAGES, INCLUDING BUT NOT LIMITED TO THE
 *     COST OF PROCURING SUBSTITUTE GOODS OR SERVICES, LOST PROFITS, LOSS
 *     OF USE, LOSS OF DATA, OR ANY INCIDENTAL, CONSEQUENTIAL, DIRECT,
 *     INDIRECT, OR SPECIAL DAMAGES WHETHER UNDER CONTRACT, TORT, WARRANTY,
 *     OR OTHERWISE, ARISING IN ANY WAY OUT OF THIS OR ANY OTHER AGREEMENT
 *     RELATING TO THE WORK, WHETHER OR NOT SUCH AUTHOR OR DEVELOPER HAD
 *     ADVANCE NOTICE OF THE POSSIBILITY OF SUCH DAMAGES.
 *
 * Contributors:
 * Gradient Systems
 */

/*** includes ***/
#include "config.h"
#include "porting.h"
#include <stdlib.h>
#ifndef USE_STDLIB_H
#include <malloc.h>
#endif
#include <stdio.h>
#include <math.h>
#include "date.h"
#include "mathops.h"
#include "dist.h"

#define D_CHARS      "ymdYMD24" /* valid characters in a DBGDATE setting */
#define MIN_DATE_INT 18000101

static int m_days[2][13] = {{0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
                            {0, 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}};
static char *qtr_start[5] = {NULL, "01-01", "04-01", "07-01", "10-01"};
char *weekday_names[8] = {NULL, "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
/*
 * Routine: mk_date(void)
 * Purpose: initialize a date_t
 * Algorithm:
 * Data Structures:
 * Params:
 * Returns: date_t *
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
date_t *mk_date(void) {
	date_t *res;

	res = (date_t *)malloc(sizeof(struct DATE_T));
	MALLOC_CHECK(res);

	res->flags = 0;
	res->year = 0;
	res->month = 0;
	res->day = 0;
	res->julian = 0;

	return (res);
}
/*
 * Routine: strtotime(char *str)
 * Purpose: convert a string from the time to the number of seconds since
 * midnight Algorithm: Data Structures: Params: Returns: int Called By: Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int strtotime(char *str) {
	int hour, min, sec, res;

	if (sscanf(str, "%d:%d:%d", &hour, &min, &sec) != 3) {
		if (sscanf(str, "%d:%d", &hour, &min) != 2) {
			INTERNAL("Invalid time format");
		}
		sec = 0;
	}

	if (hour > 23 || hour < 0)
		INTERNAL("Invalid time format");
	if (min > 59 || min < 0)
		INTERNAL("Invalid time format");
	if (sec > 59 || sec < 0)
		INTERNAL("Invalid time format");

	res = hour * 3600 + min * 60 + sec;

	return (res);
}

/*
 * Routine: jtodt(int src, date_t *dest)
 * Purpose: convert a number of julian days to a date_t
 * Algorithm: Fleigel and Van Flandern (CACM, vol 11, #10, Oct. 1968, p. 657)
 * Data Structures:
 *
 * Params: source integer: days since big bang
 * Returns: date_t *; NULL on failure
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO:
 */
int jtodt(date_t *dest, int src) {
	long i, j, l, n;

	if (src < 0)
		return (-1);

	dest->julian = src;
	l = src + 68569;
	n = (int)floor((4 * l) / 146097);
	l = l - (int)floor((146097 * n + 3) / 4);
	i = (int)floor((4000 * (l + 1) / 1461001));
	l = l - (int)floor((1461 * i) / 4) + 31;
	j = (int)floor((80 * l) / 2447);
	dest->day = l - (int)floor((2447 * j) / 80);
	l = (int)floor(j / 11);
	dest->month = j + 2 - 12 * l;
	dest->year = 100 * (n - 49) + i + l;

	return (0);
}

/*
 * Routine: dttoj(date_t *)
 * Purpose: convert a date_t to a number of julian days
 * Algorithm: http://quasar.as.utexas.edu/BillInfo/JulianDatesG.html
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int dttoj(date_t *dt) {
	int y, m, res;

	y = dt->year;
	m = dt->month;
	if (m <= 2) {
		m += 12;
		y -= 1;
	}

	/*
	 * added 1 to get dttoj and jtodt to match
	 */
	res = dt->day + (153 * m - 457) / 5 + 365 * y + (int)floor(y / 4) - (int)floor(y / 100) + (int)floor(y / 400) +
	      1721118 + 1;

	return (res);
}

/*
 * Routine: strtodt()
 * Purpose: Convert an ascii string to a date_t structure
 * Algorithm:
 * Data Structures:
 *
 * Params: char *s, date_t *dest
 * Returns: int; 0 on success
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: Need to allow for date formats other than Y4MD-
 */
int strtodt(date_t *dest, char *s) {
	int nRetCode = 0;

	if (s == NULL) {
		dest = NULL;
		return (-1);
	}

	if (sscanf(s, "%4d-%d-%d", &dest->year, &dest->month, &dest->day) != 3) {
		fprintf(stderr, "ERROR: Invalid string to date conversion in strtodt\n");
		nRetCode = -1;
	}

	dest->julian = dttoj(dest);

	return (nRetCode);
}

/*
 * Routine: dttostr(date_t *d)
 * Purpose: convert a date_t structure to a string
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns: char *; NULL on failure
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: 20000110 Need to handle more than Y4MD-
 */
char *dttostr(date_t *d) {
	static char *res;
	static int init = 0;

	if (!init) {
		res = (char *)malloc(sizeof(char) * 11);
		MALLOC_CHECK(res);
		init = 1;
	}

	if (d == NULL)
		return (NULL);

	sprintf(res, "%4d-%02d-%02d", d->year, d->month, d->day);

	return (res);
}

/*
 * Routine: date_init
 * Purpose: set the date handling parameters
 * Algorithm:
 * Data Structures:
 *
 * Params: None
 * Returns: int; 0 on success
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int date_init(void) {
	printf("date_init is not yet complete\n");
	exit(1);
	return (0);
}

/*
 * Routine: date_t_op(int op, date_t *operand1, date_t *operand2)
 * Purpose: execute arbitrary binary operations on date_t's
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO:
 *	20010806 jms	Return code is meaningless
 */
int date_t_op(date_t *dest, int op, date_t *d1, date_t *d2) {
	int tJulian;
	char tString[11];
	date_t tDate;

	switch (op) {
	case OP_FIRST_DOM: /* set to first day of month */
		tJulian = d1->julian - d1->day + 1;
		jtodt(dest, tJulian);
		break;
	case OP_LAST_DOM: /* set to last day of month */
		tJulian = d1->julian - d1->day + m_days[is_leap(d1->year)][d1->month];
		jtodt(dest, tJulian);
		break;
	case OP_SAME_LY:
		if (is_leap(d1->year) && (d1->month == 2) && (d1->day == 29))
			sprintf(tString, "%d-02-28", d1->year - 1);
		else
			sprintf(tString, "%4d-%02d-%02d", d1->year - 1, d1->month, d1->day);
		strtodt(dest, tString);
		break;
	case OP_SAME_LQ:
		switch (d1->month) {
		case 1:
		case 2:
		case 3:
			sprintf(tString, "%4d-%s", d1->year, qtr_start[1]);
			strtodt(&tDate, tString);
			tJulian = d1->julian - tDate.julian;
			sprintf(tString, "%4d-%s", d1->year - 1, qtr_start[4]);
			strtodt(&tDate, tString);
			tJulian += tDate.julian;
			jtodt(dest, tJulian);
			break;
		case 4:
		case 5:
		case 6:
			sprintf(tString, "%4d-%s", d1->year, qtr_start[2]);
			strtodt(&tDate, tString);
			tJulian = d1->julian - tDate.julian;
			sprintf(tString, "%4d-%s", d1->year, qtr_start[1]);
			strtodt(&tDate, tString);
			tJulian += tDate.julian;
			jtodt(dest, tJulian);
			break;
		case 7:
		case 8:
		case 9:
			sprintf(tString, "%4d-%s", d1->year, qtr_start[3]);
			strtodt(&tDate, tString);
			tJulian = d1->julian - tDate.julian;
			sprintf(tString, "%4d-%s", d1->year, qtr_start[2]);
			strtodt(&tDate, tString);
			tJulian += tDate.julian;
			jtodt(dest, tJulian);
			break;
		case 10:
		case 11:
		case 12:
			sprintf(tString, "%4d-%s", d1->year, qtr_start[4]);
			strtodt(&tDate, tString);
			tJulian = d1->julian - tDate.julian;
			sprintf(tString, "%4d-%s", d1->year, qtr_start[3]);
			strtodt(&tDate, tString);
			tJulian += tDate.julian;
			jtodt(dest, tJulian);
			break;
		}
		break;
	}

	return (0);
}

/*
 * Routine: itodt(date_t *d, int src)
 * Purpose:  convert a number of days to a date_t
 * Algorithm: NOTE: sets only julian field
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int itodt(date_t *dest, int src) {

	dest->julian = src;

	return (0);
}

/*
 * Routine: set_dow(date *d)
 * Purpose: perpetual calendar stuff
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO:
 */
static int doomsday[4] = {3, 2, 0, 5};
static int known[13] = {0, 3, 0, 0, 4, 9, 6, 11, 8, 5, 10, 7, 12};
int set_dow(date_t *d) {

	static int last_year = -1, dday;
	int res, q, r, s;

	if (d->year != last_year) {
		if (is_leap(d->year)) {
			/* adjust the known dates for january and february */
			known[1] = 4;
			known[2] = 1;
		} else {
			known[1] = 3;
			known[2] = 0;
		}

		/* calculate the doomsday for the century */
		dday = d->year / 100;
		dday -= 15;
		dday %= 4;
		dday = doomsday[dday];

		/* and then calculate the doomsday for the year */
		q = d->year % 100;
		r = q % 12;
		q /= 12;
		s = r / 4;
		dday += q + r + s;
		dday %= 7;
		last_year = d->year;
	}

	res = d->day;
	res -= known[d->month];
	while (res < 0)
		res += 7;
	while (res > 6)
		res -= 7;

	res += dday;
	res %= 7;

	return (res);
}

/*
 * Routine: is_leap(year)
 * Purpose:
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int is_leap(int year) {

	return (((year % 100) == 0) ? ((((year % 400) % 2) == 0) ? 1 : 0) : ((year % 4) == 0) ? 1 : 0);
}

/*
 * Routine: day_number(date_t *)
 * Purpose:
 * Algorithm: NOTE: this is NOT the ordinal day in the year, but the ordinal
 *reference into the calendar distribution for the day; in particular, this
 *needs to skip over the leap day Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int day_number(date_t *d) {
	return (m_days[is_leap(d->year)][d->month] + d->day);
}

/*
 * Routine: getDateWeightFromJulian(jDay, nDistribution)
 * Purpose: return the weight associated with a particular julian date and
 * distribution Algorithm: Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int getDateWeightFromJulian(int jDay, int nDistribution) {
	date_t dTemp;
	int nDay;

	jtodt(&dTemp, jDay);
	nDay = day_number(&dTemp);

	return (dist_weight(NULL, "calendar", nDay, nDistribution + is_leap(dTemp.year)));
}

/*
 * Routine: date_part(date_t *, int part)
 * Purpose:
 * Algorithm:
 * Data Structures:
 *
 * Params:
 * Returns:
 * Called By:
 * Calls:
 * Assumptions:
 * Side Effects:
 * TODO: None
 */
int date_part(date_t *d, int part) {
	switch (part) {
	case 1:
		return (d->year);
	case 2:
		return (d->month);
	case 3:
		return (d->day);
	default:
		INTERNAL("Invalid call to date_part()");
		return (-1);
	}
}

#ifdef TEST
main() {
	date_t *d;
	int ret;

	d = mk_date();
	strtodt(d, "1776-07-04");
	ret = set_dow(d);
	printf("set_dow(\"1776-07-04\"): wanted 4 got %d\n", ret);
	if (ret != 4) {
		exit(1);
	}
	strtodt(d, "2000-01-01");
	ret = set_dow(d);
	printf("set_dow(\"2000-01-01\"): wanted 6 got %d\n", ret);
	if (ret != 6) {
		exit(1);
	}

	strtodt(d, "1970-01-01");
	if ((ret = dttoj(d)) != 2440588) {
		printf("dttoj returned %d\n", ret);
		exit(1);
	}

	d->year = 1;
	d->month = 11;
	d->date = 11;
	jtodt(d, 2440588);
	if ((d->year != 1970) || (d->month != 1) || (d->date != 1)) {
		printf("jtodt failed got: ");
		printf("%4d-%02d-%02d", d->year, d->month, d->date);
		exit(1);
	}
	return (0);
}
#endif /* TEST */
