package eu.dnetlib.utils;

import java.math.RoundingMode;
import java.text.DateFormat;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This class implements a date format conforming to RFC-3339.
 * @author thanos@di.uoa.gr
 *
 */
public class RFC3339DateFormat extends DateFormat {
	private static final long serialVersionUID = 1L;
	
	private final NumberFormat twoDigits;
	private final NumberFormat threeDigits;
	private final NumberFormat fourDigits;
	private final Pattern pattern;

	/**
	 * Construct a new RFC-3339 date format.
	 */
	public RFC3339DateFormat() {
		calendar = Calendar.getInstance();
		twoDigits = NumberFormat.getIntegerInstance();
		twoDigits.setGroupingUsed(false);
		twoDigits.setRoundingMode(RoundingMode.UNNECESSARY);
		twoDigits.setMaximumIntegerDigits(2);
		twoDigits.setMinimumIntegerDigits(2);
		threeDigits = NumberFormat.getIntegerInstance();
		threeDigits.setGroupingUsed(false);
		threeDigits.setRoundingMode(RoundingMode.UNNECESSARY);
		threeDigits.setMaximumIntegerDigits(3);
		threeDigits.setMinimumIntegerDigits(3);
		fourDigits = NumberFormat.getIntegerInstance();
		fourDigits.setGroupingUsed(false);
		fourDigits.setRoundingMode(RoundingMode.UNNECESSARY);
		fourDigits.setMaximumIntegerDigits(4);
		fourDigits.setMinimumIntegerDigits(4);
		pattern = Pattern.compile("^(\\d{4})\\-(\\d{2})\\-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{3})(Z|\\+(\\d{2}):(\\d{2})|\\-(\\d{2}):(\\d{2}))$");
	}

	@Override
	public StringBuffer format(final Date date, final StringBuffer toAppendTo, final FieldPosition fieldPosition) {
		calendar.setTime(date);
		final int beginIndex = toAppendTo.length();
		toAppendTo.append(fourDigits.format(calendar.get(Calendar.YEAR)));
		toAppendTo.append("-");
		toAppendTo.append(twoDigits.format(calendar.get(Calendar.MONTH) + 1));
		toAppendTo.append("-");
		toAppendTo.append(twoDigits.format(calendar.get(Calendar.DAY_OF_MONTH)));
		toAppendTo.append("T");
		toAppendTo.append(twoDigits.format(calendar.get(Calendar.HOUR_OF_DAY)));
		toAppendTo.append(":");
		toAppendTo.append(twoDigits.format(calendar.get(Calendar.MINUTE)));
		toAppendTo.append(":");
		toAppendTo.append(twoDigits.format(calendar.get(Calendar.SECOND)));
		toAppendTo.append(".");
		toAppendTo.append(threeDigits.format(calendar.get(Calendar.MILLISECOND)));
		final int offset = calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET);
		if (offset == 0)
			toAppendTo.append("Z");
		else {
			toAppendTo.append((offset > 0) ? "+" : "-");
			toAppendTo.append(twoDigits.format(offset / 3600000)); // convert milliseconds to hours
			toAppendTo.append(":");
			toAppendTo.append(twoDigits.format((offset % 3600000) / 60000)); // convert remaining milliseconds to minutes
		}
		switch (fieldPosition.getField()) {
		case DateFormat.YEAR_FIELD:
			fieldPosition.setBeginIndex(beginIndex);
			fieldPosition.setEndIndex(beginIndex + 4);
			break;
		case DateFormat.MONTH_FIELD:
			fieldPosition.setBeginIndex(beginIndex + 5);
			fieldPosition.setEndIndex(beginIndex + 7);
			break;
		case DateFormat.DATE_FIELD:
			fieldPosition.setBeginIndex(beginIndex + 8);
			fieldPosition.setEndIndex(beginIndex + 10);
			break;
		case DateFormat.HOUR_OF_DAY0_FIELD:
			fieldPosition.setBeginIndex(beginIndex + 11);
			fieldPosition.setEndIndex(beginIndex + 13);
			break;
		case DateFormat.MINUTE_FIELD:
			fieldPosition.setBeginIndex(beginIndex + 14);
			fieldPosition.setEndIndex(beginIndex + 16);
			break;
		case DateFormat.SECOND_FIELD:
			fieldPosition.setBeginIndex(beginIndex + 17);
			fieldPosition.setEndIndex(beginIndex + 19);
			break;
		case DateFormat.MILLISECOND_FIELD:
			fieldPosition.setBeginIndex(beginIndex + 20);
			fieldPosition.setEndIndex(beginIndex + 23);
		case DateFormat.TIMEZONE_FIELD:
			fieldPosition.setBeginIndex(beginIndex + 23);
			fieldPosition.setEndIndex(toAppendTo.length());
		}
		return toAppendTo;
	}

	@Override
	public Date parse(final String source, final ParsePosition parsePosition) {
		Matcher matcher = pattern.matcher(source.substring(parsePosition.getIndex()));
		if (!matcher.matches()) {
			parsePosition.setErrorIndex(parsePosition.getIndex());
			return null;
		}
		try {
			calendar.set(Calendar.YEAR, fourDigits.parse(matcher.group(1)).intValue());
			calendar.set(Calendar.MONTH, twoDigits.parse(matcher.group(2)).intValue() - 1);
			calendar.set(Calendar.DAY_OF_MONTH, twoDigits.parse(matcher.group(3)).intValue());
			calendar.set(Calendar.HOUR_OF_DAY, twoDigits.parse(matcher.group(4)).intValue());
			calendar.set(Calendar.MINUTE, twoDigits.parse(matcher.group(5)).intValue());
			calendar.set(Calendar.SECOND, twoDigits.parse(matcher.group(6)).intValue());
			calendar.set(Calendar.MILLISECOND, threeDigits.parse(matcher.group(7)).intValue());
			if (matcher.group(8).equals("Z"))
				calendar.set(Calendar.ZONE_OFFSET, 0);
			else if (matcher.group(8).startsWith("+"))
				calendar.set(Calendar.ZONE_OFFSET, (twoDigits.parse(matcher.group(9)).intValue() * 60 + twoDigits.parse(matcher.group(10)).intValue()) * 60000);
			else if (matcher.group(8).startsWith("-"))
				calendar.set(Calendar.ZONE_OFFSET, -((twoDigits.parse(matcher.group(11)).intValue() * 60 + twoDigits.parse(matcher.group(12)).intValue()) * 60000));
			calendar.set(Calendar.DST_OFFSET, 0);
			parsePosition.setIndex(parsePosition.getIndex() + matcher.group(0).length());
			return new Date(calendar.getTimeInMillis());
		} catch (ParseException e) {
			parsePosition.setErrorIndex(parsePosition.getIndex() + e.getErrorOffset());
			return null;
		}
	}
}
