package com.hexapixel.test;

import static junit.framework.Assert.assertEquals;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Locale;

import org.eclipse.nebula.widgets.calendarcombo.DateHelper;
import org.junit.Test;

/**
 * Unit test for testing Date parsing in CalendarCombo. This test loops through each locale and runs a set of parsing tests on each locale. 
 * 
 * @author Emil
 *
 */
public class CalendarComboUnitTest {

	/**
	 * These are all tests that will be run. Custom tests are run once whereas all other tests are run once per locale that exists on the system. 
	 */
	private static enum Tests {
		// these are method parsing tests
		NUMERIC_VERY_SHORT_YEAR("Numeric Parsing - Very Short Year", 1, true, false), // not run, it's not a logical test, consider a numeric date of 8121, which could be lots of dates. 
		NUMERIC_SHORT_YEAR("Numeric Parsing - Short Year", 2, false),
		NUMERIC_LONG_YEAR("Numeric Parsing - Long Year", 4, false),

		NUMERIC_VERY_SHORT_YEAR_SEPARATED("Numeric Parsing - Very Short Year - Separated", 1, true, false), // not run by default
		NUMERIC_SHORT_YEAR_SEPARATED("Numeric Parsing - Short Year - Separated", 2, false),
		NUMERIC_LONG_YEAR_SEPARATED("Numeric Parsing - Long Year - Separated", 4, false),

		NUMERIC_VERY_SHORT_YEAR_SEPARATED_WRONG("Numeric Parsing - Very Short Year - Separated [Wrong Separator]", 1, true, false), // not run by default
		NUMERIC_SHORT_YEAR_SEPARATED_WRONG("Numeric Parsing - Short Year - Separated [Wrong Separator]", 2, false),
		NUMERIC_LONG_YEAR_SEPARATED_WRONG("Numeric Parsing - Long Year - Separated [Wrong Separator]", 4, false),

		HARD_PARSE("Hard Parsing", 4, false),
		NORMAL_PARSE("Normal Parsing", 4, false),

		// these are actual tests parsing like the combo would parse it
		CALENDAR_COMBO_PARSE_SHORT("Calendar Combo Parse - Short", 2, false),
		CALENDAR_COMBO_PARSE_LONG("Calendar Combo Parse - Long", 4, false),
		CALENDAR_COMBO_PARSE_NUMERIC_SHORT("Numeric Calendar Combo Parse - Short", 2, false),
		CALENDAR_COMBO_PARSE_NUMERIC_LONG("Numeric Calendar Combo Parse - Long", 4, false),

		// custom tests, some that were reported problematic by users, good to keep in for ensuring it all works great
		CUSTOM_1(new Locale("ro_RO"), "01/02/03", 2003, 01, 02, false),
		CUSTOM_2(Locale.GERMAN, "06.07.2008", 2008, 07, 06, false);

		private String	_testName;
		private int		_yearLength;
		private boolean	_canFail;
		private boolean	_runTest	= true;
		private boolean	_customParse;
		private Locale	_locale;
		private String	_parseString;
		private int		_year;
		private int		_month;
		private int		_day;
		
		private Tests(Locale locale, String toParse, int year, int month, int day, boolean canFail) {
			_customParse = true;
			_locale = locale;
			_parseString = toParse;
			_month = month - 1;
			_year = year;
			_day = day;
			_canFail = canFail;
			_testName = "Custom Test @ Location " + ordinal();
		}

		private Tests(String str, int yearLength, boolean canFail) {
			_testName = str;
			_yearLength = yearLength;
			_canFail = canFail;
		}

		private Tests(String str, int yearLength, boolean canFail, boolean runTest) {
			_testName = str;
			_yearLength = yearLength;
			_canFail = canFail;
			_runTest = runTest;
		}
		
		public Calendar getCustomCalendar() {
			Calendar cal = Calendar.getInstance(_locale);
			cal.set(Calendar.YEAR, _year);
			cal.set(Calendar.MONTH, _month);
			cal.set(Calendar.DATE, _day);
			cal.set(Calendar.HOUR_OF_DAY, 0);
			cal.set(Calendar.MINUTE, 0);
			cal.set(Calendar.SECOND, 0);
			cal.set(Calendar.MILLISECOND, 0);
			return cal;
		}

		public int getYearLength() {
			return _yearLength;
		}

		public String getTestName() {
			return _testName;
		}

		public boolean canFail() {
			return _canFail;
		}

		public boolean runTest() {
			return _runTest;
		}

		public boolean isCustomParse() {
			return _customParse;
		}

		public Locale getLocale() {
			return _locale;
		}

		public String getParseString() {
			return _parseString;
		}

		public int getYear() {
			return _year;
		}

		public int getMonth() {
			return _month;
		}

		public int getDay() {
			return _day;
		}

	}

	@Test
	public void runLocaleTest() {

		Locale[] locales = Locale.getAvailableLocales();
		for (int i = 0; i < locales.length; i++) {
			Locale l = locales[i];
			DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, l);
			String pattern = ((SimpleDateFormat) df).toPattern();

			System.out.println("--------- Testing Locale: " + l + " -----------");
			for (Tests test : Tests.values()) {
				if (!test.runTest())
					continue;

				if (test.isCustomParse())
					continue;

				boolean ret = runParseTest(pattern, l, test);
				if (!ret && test.canFail()) {
					System.err.println("[" + l + "] Test failed but was allowed to fail");
				}
				else {
					assertEquals(true, ret);
				}				
			}
			
			System.out.println();
		}

		System.out.println("---- Running Custom Tests ----\n");
		
		for (Tests test : Tests.values()) {
			if (!test.isCustomParse())
				continue;

			Locale l = test.getLocale();
			DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, l);
			String pattern = ((SimpleDateFormat) df).toPattern();

			boolean ret = runParseTest(pattern, l, test);
			if (!ret && test.canFail()) {
				System.err.println("[" + l + "] Test failed but was allowed to fail");
			}
			else {
				assertEquals(true, ret);
			}

			System.out.println();
		}

		System.out.println("==== Tested " + locales.length + " locales. All tests PASSED! ====");
		/*
		 * for (Locale l : failedLocales) { System.out.println("Failed " + l); }
		 */
	}

	private boolean runParseTest(final String pattern, final Locale l, final Tests test) {
		System.out.println("[" + l + "] --- Test: " + test + " (" + test.getTestName() + ")");

		// http://java.sun.com/javase/6/docs/technotes/guides/intl/calendar.doc.html
		if (l.toString().equals("ja_JP_JP")) {
			System.out.println("Ignoring Japanese Imperial Calendar");
			return true;
		}
		if (l.toString().equals("th_TH") || l.toString().equals("th_TH_TH")) {
			System.out.println("Ignoring Thai Buddist Calendar");
			return true;
		}
		if (l.toString().equals("zh_HK")) {
			System.out.println("Ignoring Chinese HongKong Locale");
			return true;
		}

		final int year = 2008;
		final int month = 1;
		final int day = 1;
		Calendar root = Calendar.getInstance(l);
		root.set(Calendar.YEAR, year);
		root.set(Calendar.MONTH, month - 1);
		root.set(Calendar.DATE, day);
		root.set(Calendar.ERA, GregorianCalendar.AD);
		root.set(Calendar.HOUR_OF_DAY, 0);
		root.set(Calendar.MINUTE, 0);
		root.set(Calendar.SECOND, 0);
		root.set(Calendar.MILLISECOND, 0);

		String toModify = pattern;

		if (toModify.indexOf("MM") > -1) {
			toModify = toModify.replaceAll("MM", pad(month));
		}
		else if (toModify.indexOf("M") > -1) {
			toModify = toModify.replaceAll("M", "" + pad(month));
		}
		else {
			System.err.println("No Month found in date format!");
			return false;
		}

		if (toModify.indexOf("dd") > -1) {
			toModify = toModify.replaceAll("dd", pad(day));
		}
		else if (toModify.indexOf("d") > -1) {
			toModify = toModify.replaceAll("d", "" + pad(day));
		}
		else {
			System.err.println("No Day found in date format!");
			return false;
		}

		String toReplace = "";
		if (toModify.indexOf("yyyy") > -1) {
			toReplace = "yyyy";
		}
		else if (toModify.indexOf("yy") > -1) {
			toReplace = "yy";
		}
		else if (toModify.indexOf("y") > -1) {
			toReplace = "y";
		}
		else {
			System.err.println("No Year found in date format!");
			return false;
		}

		int yLength = test.getYearLength();
		String yr = "";
		if (yLength == 1) {
			yr = "" + (year - 2000);
		}
		else if (yLength == 2) {
			yr = "0" + (year - 2000);
		}
		else if (yLength == 4) {
			yr = "" + year;
		}
		else {
			if (!test.isCustomParse()) {
				System.err.println("Year length is zero or not a number recognized");
				return false;
			}
		}
		toModify = toModify.replaceAll(toReplace, yr);

		Calendar returned = null;

		if (test.isCustomParse()) {
			System.out.println("[" + l + "] Date Pattern: " + pattern);
			System.out.println("[" + l + "] String to test: " + test.getParseString());
			try {
				returned = DateHelper.parse(test.getParseString(), test.getLocale(), pattern, new char[] {
						'/', '-', '.'
				}, null);

				root = test.getCustomCalendar();
			}
			catch (Exception err) {
				System.err.println("[" + l + "] Parsing failed");
				return false;
			}
		}
		else {
			switch (test) {
				case NUMERIC_VERY_SHORT_YEAR:
				case NUMERIC_SHORT_YEAR:
				case NUMERIC_LONG_YEAR: {
					String stest = toModify.replaceAll("\\.|\\/|-| ", "");
					stest = stest.replaceAll("[A-Za-z]", "");

					System.out.println("[" + l + "] Date Pattern: " + pattern);
					System.out.println("[" + l + "] Altered Pattern: " + toModify);
					System.out.println("[" + l + "] String to test: " + stest);

					try {
						returned = DateHelper.numericParse(stest, l, false);
					}
					catch (Exception e) {
						System.err.println("[" + l + "] Parsing failed");
						return false;
					}
				}
					break;
				case NUMERIC_VERY_SHORT_YEAR_SEPARATED:
				case NUMERIC_SHORT_YEAR_SEPARATED:
				case NUMERIC_LONG_YEAR_SEPARATED: {
					String stest = toModify.replaceAll("[A-Za-z]", "");
					System.out.println("[" + l + "] Date Pattern: " + pattern);
					System.out.println("[" + l + "] Altered Pattern: " + toModify);
					System.out.println("[" + l + "] String to test: " + stest);

					try {
						returned = DateHelper.parseDateHard(stest, l);
					}
					catch (Exception e) {
						System.err.println("[" + l + "] Parsing failed");
						return false;
					}
				}
					break;
				case NUMERIC_VERY_SHORT_YEAR_SEPARATED_WRONG:
				case NUMERIC_LONG_YEAR_SEPARATED_WRONG:
				case NUMERIC_SHORT_YEAR_SEPARATED_WRONG: {
					String stest = toModify.replaceAll("[A-Za-z]", "");
					String sep = "";
					if (stest.indexOf(".") > -1) {
						stest = stest.replaceAll("\\.", "/");
						sep = "/";
					}
					else if (stest.indexOf("/") > -1) {
						stest = stest.replaceAll("\\/", ".");
						sep = ".";
					}
					else if (stest.indexOf("-") > -1) {
						stest = stest.replaceAll("\\-", ".");
						sep = ".";
					}

					System.out.println("[" + l + "] Date Pattern: " + pattern);
					System.out.println("[" + l + "] Altered Pattern: " + toModify + ". Using different Separator for test >> " + sep);
					System.out.println("[" + l + "] String to test: " + stest);

					try {
						returned = DateHelper.parseDateHard(stest, l);
					}
					catch (Exception e) {
						System.err.println("[" + l + "] Parsing failed");
						return false;
					}
				}
					break;
				case NORMAL_PARSE: {
					try {
						System.out.println("[" + l + "] DateFormat.SHORT is: " + pattern);
						System.out.println("[" + l + "] Altered Pattern: " + toModify);
						System.out.println("[" + l + "] String to test: " + toModify);
						returned = DateHelper.parseDate(toModify, l);
					}
					catch (Exception err) {
						System.err.println("[" + l + "] Parsing failed");
						return false;
					}
				}
					break;
				case HARD_PARSE: {
					try {
						System.out.println("[" + l + "] String to test: " + toModify);
						returned = DateHelper.parseDateHard(toModify, l);
					}
					catch (Exception err) {
						System.err.println("[" + l + "] Parsing failed");
						return false;
					}
				}
					break;
				case CALENDAR_COMBO_PARSE_SHORT:
				case CALENDAR_COMBO_PARSE_LONG: {
					try {
						System.out.println("[" + l + "] String to test: " + toModify);
						returned = DateHelper.parse(toModify, l, pattern, new char[] {
								'/', '-', '.'
						}, null);
					}
					catch (Exception err) {
						System.err.println("[" + l + "] Parsing failed");
						return false;
					}
				}
					break;
				case CALENDAR_COMBO_PARSE_NUMERIC_LONG:
				case CALENDAR_COMBO_PARSE_NUMERIC_SHORT: {
					String stest = toModify.replaceAll("\\.|\\/|-| ", "");
					stest = stest.replaceAll("[A-Za-z]", "");

					System.out.println("[" + l + "] Date Pattern: " + pattern);
					System.out.println("[" + l + "] Altered Pattern: " + toModify);
					System.out.println("[" + l + "] String to test: " + stest);

					try {
						returned = DateHelper.numericParse(stest, l, false);
					}
					catch (Exception e) {
						System.err.println("[" + l + "] Parsing failed");
						return false;
					}
				}
					break;
				default:
					System.err.println("Unknown test " + test);
					break;
			}
		}

		if (returned == null) {
			System.err.println("[" + l + "] Parsing failed - Date returned was null");
			return false;
		}
		else {
			if (!root.getTime().equals(returned.getTime())) {
				System.err.println("[" + l + "] Date parsed was " + returned.getTime());
				System.err.println(test + " - Calendars did not match for Locale '" + l + "', root calendar: " + root.getTime() + ". Parsed calendar: " + returned.getTime());
				return false;
			}
		}

		return true;
	}

	private String pad(int val) {
		return val < 10 ? "0" + val : "" + val;
	}

}

