package andreluiznsilva.architecture.java.jee7.infra.architecture.localization;

import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertSame;

import static andreluiznsilva.architecture.java.jee7.infra.architecture.localization.LocalizationProvider.*;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import andreluiznsilva.architecture.java.jee7.infra.architecture.localization.LocalizationProvider.FormatStyle;
import andreluiznsilva.architecture.java.jee7.test.UnitTestRunner;

@RunWith(UnitTestRunner.class)
public abstract class AbstractLocalizationProviderUT {

	protected LocalizationProvider beanUnderTest;

	@Before
	public void setUp() {
		beanUnderTest = createBean(LOCALE_FR_FR, TIMEZONE_UTC);
	}

	@Test
	public void testFormatCurrency() {

		final Number value = 1.234;

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final String result1 = beanUnderTest.formatCurrency(value);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final String result2 = beanUnderTest.formatCurrency(value);

		assertEquals("R$ 1,23", result1);
		assertEquals("$1.23", result2);

	}

	@Test
	public void testFormatDateFull() {
		testFormatDate(FormatStyle.FULL, "Domingo, 22 de Julho de 1984", "Sunday, July 22, 1984");
	}

	@Test
	public void testFormatDateIso() {

		final String expected = "2007-03-01T13:00:00";

		final Date value = date(expected, PATTERN_ISO_DATE);

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final String result1 = beanUnderTest.formatDateIso(value);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final String result2 = beanUnderTest.formatDateIso(value);

		assertEquals(expected, result1);
		assertEquals(expected, result2);

	}

	@Test
	public void testFormatDateLong() {
		testFormatDate(FormatStyle.LONG, "22 de Julho de 1984", "July 22, 1984");
	}

	@Test
	public void testFormatDateMedium() {
		testFormatDate(FormatStyle.MEDIUM, "22/07/1984", "Jul 22, 1984");
	}

	@Test
	public void testFormatDateShort() {
		testFormatDate(FormatStyle.SHORT, "22/07/84", "7/22/84");
	}

	@Test
	public void testFormatDateTimeFull() {
		testFormatDateTime(FormatStyle.FULL, "Domingo, 22 de Julho de 1984 22h35min12s UTC", "Sunday, July 22, 1984 10:35:12 PM UTC");
	}

	@Test
	public void testFormatDateTimeLong() {
		testFormatDateTime(FormatStyle.LONG, "22 de Julho de 1984 22h35min12s UTC", "July 22, 1984 10:35:12 PM UTC");
	}

	@Test
	public void testFormatDateTimeMedium() {
		testFormatDateTime(FormatStyle.MEDIUM, "22/07/1984 22:35:12", "Jul 22, 1984 10:35:12 PM");
	}

	@Test
	public void testFormatDateTimeShort() {
		testFormatDateTime(FormatStyle.SHORT, "22/07/84 22:35", "7/22/84 10:35 PM");
	}

	@Test
	public void testFormatNumber() {

		final Number value = 1.2345;

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final String result1 = beanUnderTest.formatNumber(value);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final String result2 = beanUnderTest.formatNumber(value);

		assertEquals("1,23", result1);
		assertEquals("1.23", result2);

	}

	@Test
	public void testFormatNumberWithDigits() {

		final Integer digits = 2;
		final Number value = 1.2345;

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final String result1 = beanUnderTest.formatNumber(value, digits);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final String result2 = beanUnderTest.formatNumber(value, digits);

		assertEquals("1,23", result1);
		assertEquals("1.23", result2);

	}

	@Test
	public void testFormatTimeFull() {
		testFormatTime(FormatStyle.FULL, "22h57min34s UTC", "10:57:34 PM UTC");
	}

	@Test
	public void testFormatTimeLong() {
		testFormatTime(FormatStyle.LONG, "22h57min34s UTC", "10:57:34 PM UTC");
	}

	@Test
	public void testFormatTimeMedium() {
		testFormatTime(FormatStyle.MEDIUM, "22:57:34", "10:57:34 PM");
	}

	@Test
	public void testFormatTimeShort() {
		testFormatTime(FormatStyle.SHORT, "22:57", "10:57 PM");
	}

	@Test
	public void testGetLocaleLocale_pt_BR() {

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);

		final Locale result = beanUnderTest.getLocale();

		assertSame(LOCALE_PT_BR, result);

	}

	@Test
	public void testGetLocaleLocale_en_US() {

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);

		final Locale result = beanUnderTest.getLocale();

		assertSame(LOCALE_EN_US, result);

	}

	@Test
	public void testGetMessageNoParameters() {

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final String result1 = beanUnderTest.getMessage("test.1");

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final String result2 = beanUnderTest.getMessage("test.1");

		assertEquals("olá 1", result1);
		assertEquals("hello 1", result2);

	}

	@Test
	public void testGetMessageNotFound() {

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final String result1 = beanUnderTest.getMessage("test.3");

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final String result2 = beanUnderTest.getMessage("test.3");

		assertEquals("test.3", result1);
		assertEquals("test.3", result2);

	}

	@Test
	public void testGetMessageNotFoundWithParameters() {

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final String result1 = beanUnderTest.getMessage("test.3", LOCALE_PT_BR);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final String result2 = beanUnderTest.getMessage("test.3", LOCALE_EN_US);

		assertEquals("test.3", result1);
		assertEquals("test.3", result2);

	}

	@Test
	public void testGetMessageWithParameters() {

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final String result1 = beanUnderTest.getMessage("test.2", LOCALE_PT_BR);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final String result2 = beanUnderTest.getMessage("test.2", LOCALE_EN_US);

		assertEquals("olá pt_BR", result1);
		assertEquals("hello en_US", result2);

	}

	@Test
	public void testGetTimeZone() {

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);

		final TimeZone result = beanUnderTest.getTimeZone();

		assertEquals(TIMEZONE_UTC, result);

	}

	@Test
	public void testGetTimeZoneEmpty() {

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);

		final TimeZone result = beanUnderTest.getTimeZone();

		assertEquals(TIMEZONE_UTC, result);

	}

	@Test
	public void testParseCurrency() {

		final String value1 = "R$ 1,23";
		final String value2 = "$1.23";

		final Number expected = 1.23;

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final Number result1 = beanUnderTest.parseCurrency(value1);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final Number result2 = beanUnderTest.parseCurrency(value2);

		assertEquals(expected, result1);
		assertEquals(expected, result2);

	}

	@Test
	public void testParseDate() {

		final String expected1 = "Quarta-feira, 3 de Janeiro de 2007 13h15min25s UTC";
		final String expected2 = "Wednesday, January 3, 2007 1:15:25 PM UTC";

		final Date value = date("03/01/2007 13:15:25", "dd/MM/yyyy HH:mm:ss");

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final String result1 = beanUnderTest.formatDateTime(value, FormatStyle.FULL, FormatStyle.FULL);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final String result2 = beanUnderTest.formatDateTime(value, FormatStyle.FULL, FormatStyle.FULL);

		assertEquals(expected1, result1);
		assertEquals(expected2, result2);

	}

	@Test
	public void testParseDateIso() {

		final String value = "2007-03-01T13:00:00";

		final Date expected = date(value, PATTERN_ISO_DATE);

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final Date result1 = beanUnderTest.parseDateIso(value);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final Date result2 = beanUnderTest.parseDateIso(value);

		assertEquals(expected, result1);
		assertEquals(expected, result2);

	}

	@Test
	public void testParseDateTime() {

		final String value1 = "Sexta-feira, 03 de Janeiro de 2007 13h15min25s UTC";
		final String value2 = "Friday, January 03, 2007 1:15:25 PM UTC";

		final Date expected = date("03/01/2007 13:15:25", "dd/MM/yyyy HH:mm:ss");

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final Date result1 = beanUnderTest.parseDateTime(value1, FormatStyle.FULL, FormatStyle.FULL);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final Date result2 = beanUnderTest.parseDateTime(value2, FormatStyle.FULL, FormatStyle.FULL);

		assertEquals(expected, result1);
		assertEquals(expected, result2);

	}

	@Test
	public void testParseNumber() {

		final String value1 = "1,23";
		final String value2 = "1.23";

		final Number expected = 1.23;

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final Number result1 = beanUnderTest.parseNumber(value1);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final Number result2 = beanUnderTest.parseNumber(value2);

		assertEquals(expected, result1);
		assertEquals(expected, result2);

	}

	@Test
	public void testParseNumberWithDigits() {

		final String value1 = "1,2345";
		final String value2 = "1.2345";

		final Number expected = 1.235;
		final Integer digits = 3;

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final Number result1 = beanUnderTest.parseNumber(value1, digits);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final Number result2 = beanUnderTest.parseNumber(value2, digits);

		assertEquals(expected.doubleValue(), result1.doubleValue(), 2);
		assertEquals(expected.doubleValue(), result2.doubleValue(), 2);

	}

	@Test
	public void testParseTimeFull() {
		testParseTime(FormatStyle.FULL, "22h57min34s UTC", "HH'h'mm'min'ss's' z", "10:57:34 PM UTC", "h:mm:ss a z");
	}

	@Test
	public void testParseTimeLongFull() {
		testParseTime(FormatStyle.LONG, "22h57min34s UTC", "HH'h'mm'min'ss's' z", "10:57:34 PM UTC", "h:mm:ss a z");
	}

	@Test
	public void testParseTimeMedium() {
		testParseTime(FormatStyle.MEDIUM, "22:57:34", "HH:mm:ss", "10:57:34 PM", "h:mm:ss a");
	}

	@Test
	public void testParseTimeShort() {
		testParseTime(FormatStyle.SHORT, "22:57", "HH:mm", "10:57 PM", "h:mm a");
	}

	protected abstract void changeLocaleAndTimezone(final Locale locale, final TimeZone timeZone);

	protected abstract LocalizationProvider createBean(Locale locale, TimeZone timeZone);

	private Date date(final String value, final String pattern) {
		try {
			SimpleDateFormat format = new SimpleDateFormat(pattern);
			format.setTimeZone(TIMEZONE_UTC);
			return format.parse(value);
		} catch (final ParseException e) {
			throw new RuntimeException(e);
		}
	}

	private void testFormatDate(final FormatStyle style, final String expected1, final String expected2) {

		final Date value = date("22/07/1984", "dd/MM/yyyy");

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final String result1 = beanUnderTest.formatDate(value, style);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final String result2 = beanUnderTest.formatDate(value, style);

		assertEquals(expected1, result1);
		assertEquals(expected2, result2);

	}

	private void testFormatDateTime(final FormatStyle style, final String expected1, final String expected2) {

		final Date value = date("22/07/1984 22:35:12", "dd/MM/yyyy HH:mm:ss");

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final String result1 = beanUnderTest.formatDateTime(value, style, style);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final String result2 = beanUnderTest.formatDateTime(value, style, style);

		assertEquals(expected1, result1);
		assertEquals(expected2, result2);

	}

	private void testFormatTime(final FormatStyle style, final String expected1, final String expected2) {

		final Date value = date("22:57:34", "HH:mm:ss");

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final String result1 = beanUnderTest.formatTime(value, style);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final String result2 = beanUnderTest.formatTime(value, style);

		assertEquals(expected1, result1);
		assertEquals(expected2, result2);

	}

	private void testParseTime(final FormatStyle style, final String value1, final String pattern1, final String value2, final String pattern2) {

		final Date expected1 = date(value1, pattern1);
		final Date expected2 = date(value2, pattern2);

		changeLocaleAndTimezone(LOCALE_PT_BR, TIMEZONE_UTC);
		final Date result1 = beanUnderTest.parseTime(value1, style);

		changeLocaleAndTimezone(LOCALE_EN_US, TIMEZONE_UTC);
		final Date result2 = beanUnderTest.parseTime(value2, style);

		assertEquals(expected1, expected2);
		assertEquals(expected1, result1);
		assertEquals(expected2, result2);

	}

}