#include <ctype.h>
#include <errno.h>
#include <time.h>
#include <float.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "extern.h"
#include "dt.h"
#include "pgtypes_error.h"
#include "pgtypes_date.h"

Date
PGTYPESdate_from_timestamp(Timestamp dt)
{
	Date		dDate;

	dDate = 0; /* suppress compiler warning */

	if (TIMESTAMP_NOT_FINITE(dt))
		return 

#ifdef HAVE_INT64_TIMESTAMP
	/* Microseconds to days */
	dDate = (dt / INT64CONST(86400000000));
#else
	/* Seconds to days */
	dDate = (dt / 86400.0);
#endif

	return dDate;
}

Date
PGTYPESdate_from_asc(char *str, char **endptr)
{
	
	Date		dDate;
	fsec_t		fsec;
	struct tm	tt,
			   *tm = &tt;
	int			tzp;
	int			dtype;
	int			nf;
	char	   *field[MAXDATEFIELDS];
	int			ftype[MAXDATEFIELDS];
	char		lowstr[MAXDATELEN + 1];
	char		*realptr;
	char **ptr = (endptr != NULL) ? endptr : &realptr;
	
	bool		EuroDates = FALSE;

	errno = 0;
	if (strlen(str) >= sizeof(lowstr))
	{
		errno = PGTYPES_DATE_BAD_DATE;
		return 0;
	}

	if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf, ptr) != 0)
	 || (DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp, EuroDates) != 0))
	{
		errno = PGTYPES_DATE_BAD_DATE;
		return 0;
	}

	switch (dtype)
	{
		case DTK_DATE:
			break;

		case DTK_EPOCH:
			GetEpochTime(tm); 
			break;

		default:
			errno = PGTYPES_DATE_BAD_DATE;
			return -1;
	}

	dDate = (date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - date2j(2000, 1, 1));

	return dDate;
}

char *
PGTYPESdate_to_asc(Date dDate)
{
	struct tm       tt, *tm = &tt;
	char            buf[MAXDATELEN + 1];
	int DateStyle=0;
	bool		EuroDates = FALSE;
						   
	j2date((dDate + date2j(2000, 1, 1)), &(tm->tm_year), &(tm->tm_mon), &(tm->tm_mday));
	EncodeDateOnly(tm, DateStyle, buf, EuroDates);
	return pgtypes_strdup(buf);
}

void
PGTYPESdate_julmdy(Date jd, int* mdy)
{
	int y, m, d;
	
	j2date((int) jd, &y, &m, &d);
	mdy[0] = (short int) m;
	mdy[1] = (short int) d;
	mdy[2] = (short int) y;
}

void
PGTYPESdate_mdyjul(int* mdy, Date *jdate)
{
	/* month is mdy[0] */
	/* day   is mdy[1] */
	/* year  is mdy[2] */

	*jdate = (Date) date2j(mdy[2], mdy[0], mdy[1]);
}

int
PGTYPESdate_dayofweek(Date dDate)
{
	/*
	     Sunday:      0
	     Monday:      1
	     Tuesday:     2
	     Wednesday:   3
	     Thursday:    4
	     Friday:      5
	     Saturday:    6
	*/
	return 6-j2day(dDate+3);
}

void
PGTYPESdate_today (Date *d)
{
	struct tm	ts;

	GetCurrentDateTime(&ts);
	*d = date2j(ts.tm_year, ts.tm_mon, ts.tm_mday) - date2j(2000, 1, 1);
	return;
}

#define PGTYPES_DATE_NUM_MAX_DIGITS		20 /* should suffice for most years... */

#define PGTYPES_FMTDATE_DAY_DIGITS_LZ		1  /* LZ means "leading zeroes" */
#define PGTYPES_FMTDATE_DOW_LITERAL_SHORT	2
#define PGTYPES_FMTDATE_MONTH_DIGITS_LZ		3
#define PGTYPES_FMTDATE_MONTH_LITERAL_SHORT	4
#define PGTYPES_FMTDATE_YEAR_DIGITS_SHORT	5
#define PGTYPES_FMTDATE_YEAR_DIGITS_LONG	6

int
PGTYPESdate_fmt_asc(Date dDate, char* fmtstring, char* outbuf) {
	static struct {
		char* format;
		int component;
	} mapping[] = {
		/* format items have to be sorted according to their length,
		 * since the first pattern that matches gets replaced by its
		 * value */
			 {"ddd",  PGTYPES_FMTDATE_DOW_LITERAL_SHORT   },
			 {"dd",   PGTYPES_FMTDATE_DAY_DIGITS_LZ       },
			 {"mmm",  PGTYPES_FMTDATE_MONTH_LITERAL_SHORT },
			 {"mm",   PGTYPES_FMTDATE_MONTH_DIGITS_LZ     },
			 {"yyyy", PGTYPES_FMTDATE_YEAR_DIGITS_LONG    },
			 {"yy",   PGTYPES_FMTDATE_YEAR_DIGITS_SHORT   },
			 { NULL, 0 }
	};

	union {
		char* replace_str;
		unsigned int replace_uint;
	} replace_val;
	int replace_type;

	int i;
	int dow;
	char* start_pattern;
	struct tm tm;

	/* XXX error handling ? */
	/* copy the string over */
	strcpy(outbuf, fmtstring);

	/* get the date */
	j2date((dDate + date2j(2000, 1, 1)), &(tm.tm_year), &(tm.tm_mon), &(tm.tm_mday));
	dow = PGTYPESdate_dayofweek(dDate);

	for (i = 0; mapping[i].format != NULL; i++) {
		while ((start_pattern = strstr(outbuf, mapping[i].format)) != NULL) {
			switch(mapping[i].component) {
				case PGTYPES_FMTDATE_DOW_LITERAL_SHORT:
					replace_val.replace_str = pgtypes_date_weekdays_short[dow];
					replace_type = PGTYPES_REPLACE_STRING_CONSTANT;
					break;
				case PGTYPES_FMTDATE_DAY_DIGITS_LZ:
					replace_val.replace_uint = tm.tm_mday;
					replace_type = PGTYPES_REPLACE_UINT_2_LZ;
					break;
				case PGTYPES_FMTDATE_MONTH_LITERAL_SHORT:
					replace_val.replace_str = months[tm.tm_mon-1];
					replace_type = PGTYPES_REPLACE_STRING_CONSTANT;
					break;
				case PGTYPES_FMTDATE_MONTH_DIGITS_LZ:
					replace_val.replace_uint = tm.tm_mon;
					replace_type = PGTYPES_REPLACE_UINT_2_LZ;
					break;
				case PGTYPES_FMTDATE_YEAR_DIGITS_LONG:
					replace_val.replace_uint = tm.tm_year;
					replace_type = PGTYPES_REPLACE_UINT_4_LZ;
					break;
				case PGTYPES_FMTDATE_YEAR_DIGITS_SHORT:
					replace_val.replace_uint = tm.tm_year % 1000;
					replace_type = PGTYPES_REPLACE_UINT_2_LZ;
					break;
				default:
					/* should not happen, set something
					 * anyway */
					replace_val.replace_str = " ";
					replace_type = PGTYPES_REPLACE_STRING_CONSTANT;
			}
			switch(replace_type) {
				case PGTYPES_REPLACE_STRING_MALLOCED:
				case PGTYPES_REPLACE_STRING_CONSTANT:
					strncpy(start_pattern, replace_val.replace_str,
							strlen(replace_val.replace_str));
					if (replace_type == PGTYPES_REPLACE_STRING_MALLOCED) {
						free(replace_val.replace_str);
					}
					break;
				case PGTYPES_REPLACE_UINT:
					{
						char* t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
						if (!t) {
							return -1;
						}
						snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
								"%u", replace_val.replace_uint);
						strncpy(start_pattern, t, strlen(t));
						free(t);
					}
					break;
				case PGTYPES_REPLACE_UINT_2_LZ:
					{
						char* t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
						if (!t) {
							return -1;
						}
						snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
								"%02u", replace_val.replace_uint);
						strncpy(start_pattern, t, strlen(t));
						free(t);
					}
					break;
				case PGTYPES_REPLACE_UINT_4_LZ:
					{
						char* t = pgtypes_alloc(PGTYPES_DATE_NUM_MAX_DIGITS);
						if (!t) {
							return -1;
						}
						snprintf(t, PGTYPES_DATE_NUM_MAX_DIGITS,
								"%04u", replace_val.replace_uint);
						strncpy(start_pattern, t, strlen(t));
						free(t);
					}
					break;
				default:
					/* doesn't happen (we set
					 * replace_type to
					 * PGTYPES_REPLACE_STRING_CONSTANT
					 * in case of an error above) */
					break;
			}
		}
	}
	return 0;
}


/*
 * PGTYPESdate_defmt_asc
 *
 * function works as follows:
 *   - first we analyze the paramters
 *   - if this is a special case with no delimiters, add delimters
 *   - find the tokens. First we look for numerical values. If we have found
 *     less than 3 tokens, we check for the months' names and thereafter for
 *     the abbreviations of the months' names.
 *   - then we see which parameter should be the date, the month and the
 *     year and from these values we calculate the date
 */

#define PGTYPES_DATE_MONTH_MAXLENGTH		20  /* probably even less  :-) */
int
PGTYPESdate_defmt_asc(Date *d, char *fmt, char *str)
{
	/* token[2] = { 4,6 } means that token 2 starts at
	 * position 4 and ends at (including) position 6 */
	int token[3][2];
	int token_values[3] = { -1, -1, -1 };
	char* fmt_token_order;
	char* fmt_ystart, *fmt_mstart, *fmt_dstart;
	int i;
	int reading_digit;
	int token_count;
	char* str_copy;
	struct tm tm;

	if (!d || !str || !fmt) {
		errno = PGTYPES_DATE_ERR_EARGS;
		return -1;
	}

	/* analyze the fmt string */
	fmt_ystart = strstr(fmt, "yy");
	fmt_mstart = strstr(fmt, "mm");
	fmt_dstart = strstr(fmt, "dd");

	if (!fmt_ystart || !fmt_mstart || !fmt_dstart) {
		errno = PGTYPES_DATE_ERR_EARGS;
		return -1;
	}

	if (fmt_ystart < fmt_mstart) {
		/* y m */
		if (fmt_dstart < fmt_ystart) {
			/* d y m */
			fmt_token_order = "dym";
		} else if (fmt_dstart > fmt_mstart) {
			/* y m d */
			fmt_token_order = "ymd";
		} else {
			/* y d m */
			fmt_token_order = "ydm";
		}
	} else {
		/* fmt_ystart > fmt_mstart */
		/* m y */
		if (fmt_dstart < fmt_mstart) {
			/* d m y */
			fmt_token_order = "dmy";
		} else if (fmt_dstart > fmt_ystart) {
			/* m y d */
			fmt_token_order = "myd";
		} else {
			/* m d y */
			fmt_token_order = "mdy";
		}
	}

	/* handle the special cases where there is no delimiter between the
	 * digits. If we see this:
	 *
	 * only digits, 6 or 8 bytes then it might be ddmmyy and ddmmyyyy
	 * (or similar)
	 *
	 * we reduce it to a string with delimiters and continue processing
	 * */

	/* check if we have only digits */
	reading_digit = 1;
	for (i = 0; str[i]; i++) {
		if (!isdigit(str[i])) {
			reading_digit = 0;
			break;
		}
	}
	if (reading_digit) {
		int frag_length[3];
		int target_pos;

		i = strlen(str);
		if (i != 8 && i != 6) {
			errno = PGTYPES_DATE_ERR_ENOSHORTDATE;
			return -1;
		}
		/* okay, this really is the special case */

		/* as long as the string, one additional byte for the
		 * terminator and 2 for the delimiters between the 3 fiedls
		 * */
		str_copy = pgtypes_alloc(strlen(str) + 1 + 2);
		if (!str_copy) {
			return -1;
		}

		/* determine length of the fragments */
		if (i == 6) {
			frag_length[0] = 2; frag_length[1] = 2; frag_length[2] = 2;
		} else {
			if (fmt_token_order[0] == 'y') {
				frag_length[0] = 4; frag_length[1] = 2; frag_length[2] = 2;
			} else if (fmt_token_order[1] == 'y') {
				frag_length[0] = 2; frag_length[1] = 4; frag_length[2] = 2;
			} else {
				frag_length[0] = 2; frag_length[1] = 2; frag_length[2] = 4;
			}
		}
		target_pos = 0;
		/* XXX: Here we could calculate the positions of the tokens
		 * and save the for loop down there where we again check
		 * with isdigit() for digits. */
		for (i = 0; i < 3; i++) {
			int start_pos = 0;
			if (i >= 1) { start_pos += frag_length[0]; }
			if (i == 2) { start_pos += frag_length[1]; }

			strncpy(str_copy + target_pos, str + start_pos,
						frag_length[i]);
			target_pos += frag_length[i];
			if (i != 2) {
				str_copy[target_pos] = ' ';
				target_pos++;
			}
		}
		str_copy[target_pos] = '\0';
	} else {
		str_copy = pgtypes_strdup(str);
		if (!str_copy) {
			return -1;
		}

		/* convert the whole string to lower case */
		for (i = 0; str_copy[i]; i++) {
			str_copy[i] = (char) tolower(str_copy[i]);
		}
	}

	/* look for numerical tokens */
	reading_digit = 0;
	token_count   = 0;
	for (i = 0; i < strlen(str_copy); i++) {
		if (!isdigit(str_copy[i]) && reading_digit) {
			/* the token is finished */
			token[token_count][1] = i-1;
			reading_digit = 0;
			token_count++;
		} else if (isdigit(str_copy[i]) && !reading_digit) {
			/* we have found a token */
			token[token_count][0] = i;
			reading_digit = 1;
		}
	}
	/* we're at the end of the input string, but maybe we are still reading a
	 * number... */
	if (reading_digit) {
		token[token_count][1] = i-1;
		token_count++;
	}


	if (token_count < 2) {
		/* not all tokens found, no way to find 2 missing tokens
		 * with string matches */
		free(str_copy);
		errno = PGTYPES_DATE_ERR_ENOTDMY;
		return -1;
	}

	if (token_count != 3) {
		/* not all tokens found but we may find another one with
		 * string matches by testing for the months names and months
		 * abbreviations */
		char *month_lower_tmp = pgtypes_alloc(PGTYPES_DATE_MONTH_MAXLENGTH);
		char *start_pos;
		int j;
		int offset;
		int found = 0;
		char** list;

		if (!month_lower_tmp) {
			/* free variables we alloc'ed before */
			free(str_copy);
			return -1;
		}
		list = pgtypes_date_months;
		for (i = 0; list[i]; i++) {
			for (j = 0; j < PGTYPES_DATE_MONTH_MAXLENGTH; j++) {
				month_lower_tmp[j] = (char) tolower(list[i][j]);
				if (!month_lower_tmp[j]) {
					/* properly terminated */
					break;
				}
			}
			if ((start_pos = strstr(str_copy, month_lower_tmp))) {
				offset = start_pos - str_copy;
				/* sort the new token into the numeric
				 * tokens, shift them if necessary */
				if (offset < token[0][0]) {
					token[2][0] = token[1][0];
					token[2][1] = token[1][1];
					token[1][0] = token[0][0];
					token[1][1] = token[0][1];
					token_count = 0;
				} else if (offset < token[1][0]) {
					token[2][0] = token[1][0];
					token[2][1] = token[1][1];
					token_count = 1;
				} else {
					token_count = 2;
				}
				token[token_count][0] = offset;
				token[token_count][1] = offset + strlen(month_lower_tmp) - 1;
				/* the value is the index of the month in
				 * the array of months + 1 (January is month
				 * 0) */
				token_values[token_count] = i+1;
				found = 1;
				break;
			}
			/* evil[tm] hack:
			 * if we read the pgtypes_date_months and haven't
			 * found a match, reset list to point to
			 * pgtypes_date_months_short and reset the counter
			 * variable i */
			if (list == pgtypes_date_months) {
				if (list[i+1] == NULL) {
					list = months;
					i = -1;
				}
			}
		}
		if (!found) {
			free(month_lower_tmp);
			free(str_copy);
			errno = PGTYPES_DATE_ERR_ENOTDMY;
			return -1;
		}

		/* here we found a month. token[token_count] and
		 * token_values[token_count] reflect the month's details.
		 *
		 * only the month can be specified with a literal. Here we can do a
		 * quick check if the month is at the right position according to
		 * the format string because we can check if the token that
		 * we expect to be the month is at the position of the only
		 * token that already has a value. If we wouldn't check here
		 * we could say "December 4 1990" with a fmt string of
		 * "dd mm yy" for 12 April 1990.
		 */
		if (fmt_token_order[token_count] != 'm') {
			/* deal with the error later on */
			token_values[token_count] = -1;
		}
		free(month_lower_tmp);
	}

	/* terminate the tokens with ASCII-0 and get their values */
	for (i = 0; i < 3; i++) {
		*(str_copy + token[i][1] + 1) = '\0';
		/* A month already has a value set, check for token_value == -1 */
		if (token_values[i] == -1) {
			errno = 0;
			token_values[i] = strtol(str_copy + token[i][0], (char **) NULL, 10);
			/* strtol sets errno in case of an error */
			if (errno) {
				token_values[i] = -1;
			}
		}
		if (fmt_token_order[i] == 'd') {
			tm.tm_mday = token_values[i];
		} else if (fmt_token_order[i] == 'm') {
			tm.tm_mon = token_values[i];
		} else if (fmt_token_order[i] == 'y') {
			tm.tm_year = token_values[i];
		}
	}
	free(str_copy);

	if (tm.tm_mday < 1 || tm.tm_mday > 31)
	{
		errno = PGTYPES_DATE_BAD_DAY;
		return -1;
	}

	if (tm.tm_mon < 1 || tm.tm_mon > 12)
	{
		errno = PGTYPES_DATE_BAD_MONTH;
		return -1;
	}

	if (tm.tm_mday == 31 && (tm.tm_mon == 4 || tm.tm_mon == 6 || tm.tm_mon == 9 || tm.tm_mon ==11))
	{
		errno = PGTYPES_DATE_BAD_DAY;
		return -1;
	}

	if (tm.tm_mon == 2 && tm.tm_mday > 29)
	{
		errno = PGTYPES_DATE_BAD_DAY;
		return -1;
	}

	/* XXX: DBCENTURY ? */

	*d = date2j(tm.tm_year, tm.tm_mon, tm.tm_mday) - date2j(2000, 1, 1);

	return 0;
}
