package st.gravel.support.jvm;

import java.math.BigInteger;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.TimeZone;

public class TimeExtensions {
	private static final BigInteger ONE_MILLION = BigInteger.valueOf(1000000);
	private static final long nanoOffset = calculateNanoOffset();

	public static long nanosecondClock() {
		return System.nanoTime() + nanoOffset;
	}

	public static long clockPrecisionNS() {
		long sample = System.nanoTime();
		long prec = Long.MAX_VALUE;
		for (int samples = 0; samples < 10;) {
			long diff = System.nanoTime() - sample;
			if (diff > 0) {
				prec = Math.min(diff, prec);
				samples++;
			}
		}
		System.out.println(prec);
		return prec;
	}

	public static TimeZone defaultTimeZone() {
		return TimeZone.getDefault();
	}

	public static TimeZone getTimeZoneNamed_(String name) {
		return TimeZone.getTimeZone(name);
	}

	public static TimeZone getTimeZoneForOffsetMS_(int ms) {
		String[] availableIDs = TimeZone.getAvailableIDs(ms);
		if (availableIDs.length == 0)
			throw new RuntimeException("No timezone for offset: " + ms);
		return getTimeZoneNamed_(availableIDs[0]);
	}

	public static Calendar newCalendar_timeZone_(long millis, TimeZone timeZone) {
		Calendar calendar = Calendar.getInstance(timeZone);
		calendar.setTimeInMillis(millis);
		return calendar;
	}

	public static Calendar newCalendarFromString_(String str) throws ParseException {
		Calendar cal = Calendar.getInstance();
	    SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss.SSS");
	    cal.setTime(sdf.parse(str));
		return cal;
	}

	public static int year(Calendar cal) {
		return cal.get(Calendar.YEAR);
	}
	public static int monthIndex(Calendar cal) {
		return cal.get(Calendar.MONTH)+1;
	}
	public static int dayOfMonth(Calendar cal) {
		return cal.get(Calendar.DAY_OF_MONTH);
	}
	public static int dayOfYear(Calendar cal) {
		return cal.get(Calendar.DAY_OF_YEAR);
	}
	public static int hour(Calendar cal) {
		return cal.get(Calendar.HOUR_OF_DAY);
	}
	public static int minute(Calendar cal) {
		return cal.get(Calendar.MINUTE);
	}
	public static int second(Calendar cal) {
		return cal.get(Calendar.SECOND);
	}
	public static boolean isLeapYear(Calendar cal) {
		return new GregorianCalendar().isLeapYear(year(cal)); 
	}
	private static long calculateNanoOffset() {
		long currentTimeMillis = System.currentTimeMillis();
		long nanoTime = System.nanoTime();
		return (currentTimeMillis * 1000000) - nanoTime;
	}

	public static Object waitForNanoseconds(final Object receiver,
			Object nanosecondsObject) {
		long timeout;
		int nanos;
		if (nanosecondsObject instanceof BigInteger) {
			final BigInteger nanoseconds = (BigInteger) nanosecondsObject;
			timeout = nanoseconds.divide(ONE_MILLION).longValue();
			nanos = nanoseconds.remainder(ONE_MILLION).intValue();
		} else if (nanosecondsObject instanceof Integer) {
			final int nanoseconds = (Integer) nanosecondsObject;
			timeout = nanoseconds / 1000000;
			nanos = nanoseconds % 1000000;
		} else {
			throw new RuntimeException("Unknown parameter type");
		}
		try {
			synchronized (receiver) {
				receiver.wait(timeout, nanos);
			}
		} catch (InterruptedException e) {
			throw new RuntimeException(e);
		}
		return receiver;
	}
}