/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package woolpack.misc;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;

import junit.framework.TestCase;
import woolpack.convert.ConvertUtils;
import woolpack.fn.Fn;
import woolpack.fn.FnUtils;
import woolpack.utils.Utils;

public class MiscUtilsTest extends TestCase {

	public void testLapTime() throws Exception {
		final List<Long> list = new ArrayList<Long>();
		final Fn<Integer, String, Exception> fn = MiscUtils.lapTime(
				FnUtils.seq(Utils.<Fn<? super Integer, ? extends String, ? extends Exception>>
				list(new SleepFn<Integer, String>(100))
				.list(ConvertUtils.TO_STRING)),
				FnUtils.recode(null, list, null));
		assertEquals("4", fn.exec(4));
		assertTrue(Math.abs(list.get(0) - 100) < 10);
	}

	public void testLapTimeException() throws Exception {
		final List<Long> list = new ArrayList<Long>();
		final Fn<Integer, String, Exception> fn = MiscUtils.lapTime(
				FnUtils.seq(Utils.<Fn<? super Integer, ? extends String, ? extends Exception>>
				list(new SleepFn<Integer, String>(100))
				.list(ConvertUtils.TO_STRING)
				.list(FnUtils.<Integer, String, Exception>throwing(new IllegalStateException()))),
				FnUtils.recode(null, list, null));
		try {
			fn.exec(4);
			fail();
		} catch(final IllegalStateException e) {
		}
		assertTrue(Math.abs(list.get(0) - 100) < 10);
	}
	
	public void testSleep() throws InterruptedException {
		final Fn<String, String, InterruptedException> fn = MiscUtils.sleep(100);
		final long before = System.currentTimeMillis();
		assertNull(fn.exec("id0"));
		final long after = System.currentTimeMillis();
		assertTrue(Math.abs(Math.abs(after - before) - 100) <= 10);
	}
	
	public void testSleepException() {
		final Fn<String, String, InterruptedException> fn = MiscUtils.sleep(100);
		Thread.currentThread().interrupt();
		try {
			fn.exec("id0");
			fail();
		} catch (final InterruptedException e) {
			Thread.interrupted();
		}
	}

	public void testSwitchNearLocale() {
		{
			final Fn<Locale, String, RuntimeException> fn =
				MiscUtils.switchNearLocale(FnUtils.switching(Utils
					.map(Locale.ENGLISH, "e")
					.map(Locale.JAPANESE, "j"), "d"));
			assertEquals("e", fn.exec(Locale.ENGLISH));
			assertEquals("e", fn.exec(Locale.US));
			assertEquals("j", fn.exec(Locale.JAPANESE));
			assertEquals("j", fn.exec(Locale.JAPAN));
			assertEquals("d", fn.exec(Locale.CHINESE));
			assertEquals("d", fn.exec(null));
		}
		{
			final Fn<Locale, String, RuntimeException> fn =
				MiscUtils.switchNearLocale(FnUtils.<Locale, String>switching(Utils
					.map(new Object(), "o")
					.map(Locale.ENGLISH, "e")
					.map(Locale.JAPANESE, "j"), "d"));
			assertEquals("e", fn.exec(Locale.ENGLISH));
			assertEquals("e", fn.exec(Locale.US));
			assertEquals("j", fn.exec(Locale.JAPANESE));
			assertEquals("j", fn.exec(Locale.JAPAN));
			assertEquals("d", fn.exec(Locale.CHINESE));
			assertEquals("d", fn.exec(null));
		}
		{
			final Fn<Locale, String, RuntimeException> fn = MiscUtils.switchNearLocale(
					FnUtils.switching(Utils
					.map(Locale.ENGLISH, "e")
					.map(Locale.JAPANESE, "j")
					.map(new Locale("ja", "JP", "POSIX"), "p"), "d"));
			assertEquals("e", fn.exec(Locale.ENGLISH));
			assertEquals("e", fn.exec(Locale.US));
			assertEquals("j", fn.exec(Locale.JAPANESE));
			assertEquals("j", fn.exec(Locale.JAPAN));
			assertEquals("d", fn.exec(Locale.CHINESE));
			assertEquals("d", fn.exec(null));
			assertEquals("p", fn.exec(new Locale("ja", "JP", "POSIX")));
		}
		{
			final Fn<Locale, String, RuntimeException> fn = MiscUtils.switchNearLocale(
					FnUtils.switching(Utils
					.map(Locale.JAPAN, "japan")
					.map(Locale.JAPANESE, "japanese")
					.map(new Locale("ja", "JP", "POSIX"), "p"), "d"));
			assertEquals("p", fn.exec(new Locale("ja", "JP", "POSIX")));
			assertEquals("japan", fn.exec(new Locale("ja", "JP", "WIN")));
			assertEquals("japan", fn.exec(new Locale("ja", "JP")));
			assertEquals("japanese", fn.exec(new Locale("ja")));
			// assertEquals("japanese", switchable.get(new Locale("ja", "US")));
			assertEquals("d", fn.exec(new Locale("en")));
		}
		{
			final Fn<Locale, String, RuntimeException> fn = MiscUtils.switchNearLocale(
					FnUtils.switching(Utils
					.map(Locale.US, "e")
					.map(Locale.JAPAN, "j"), "d"));
			assertEquals("d", fn.exec(Locale.ENGLISH));
			assertEquals("e", fn.exec(Locale.US));
			assertEquals("d", fn.exec(Locale.JAPANESE));
			assertEquals("j", fn.exec(Locale.JAPAN));
			assertEquals("d", fn.exec(Locale.CHINESE));
			assertEquals("d", fn.exec(null));
		}
	}
	
	public void testThreadLocal() {
		final ThreadLocal<String> threadLocal = new ThreadLocal<String>();
		final Fn<Object, String, RuntimeException> fn = MiscUtils.threadLocal(threadLocal);
		assertNull(fn.exec(0));
		threadLocal.set("value0");
		assertEquals("value0", fn.exec(0));
	}

	public void testTryLocales() {
		final ThreadLocal<Locale> threadLocal = new ThreadLocal<Locale>();
		final List<String> result = new ArrayList<String>();
		final Object o = new Object();
		final Fn<String, Object, ? extends RuntimeException> fn = MiscUtils.tryLocales(
				FnUtils.recode(FnUtils.fix(o), "", result),
				threadLocal);
		threadLocal.set(Locale.JAPANESE);
		assertSame(o, fn.exec("id0"));
	}
	
	public void testTryLocalesSearch() {
		final ThreadLocal<Locale> threadLocal = new ThreadLocal<Locale>();
		final List<String> result = new ArrayList<String>();
		final Fn<String, Object, ? extends RuntimeException> fn = MiscUtils.tryLocales(
				FnUtils.recode(FnUtils.fix(null), result, null), threadLocal);

		final Locale defaultLocale = Locale.getDefault();
		try {
			Locale.setDefault(new Locale("ja", "JP", "POSIX"));

			result.clear();
			threadLocal.set(new Locale("en", "US", "WIN"));
			try {
				fn.exec("id0");
				fail();
			} catch (final IllegalStateException expected) {
			}
			assertEquals(Arrays.asList(
					"id0_en_US_WIN",
					"id0_en_US",
					"id0_en",
					"id0_ja_JP_POSIX",
					"id0_ja_JP",
					"id0_ja",
					"id0"), result);

			result.clear();
			threadLocal.set(null);
			try {
				fn.exec("id0");
				fail();
			} catch (final IllegalStateException expected) {
			}
			assertEquals(Arrays.asList("id0_ja_JP_POSIX", "id0_ja_JP", "id0_ja", "id0"), result);
		} finally {
			Locale.setDefault(defaultLocale);
		}
	}

	public void testTryLocalesIOException() {
		final ThreadLocal<Locale> threadLocal = new ThreadLocal<Locale>();
		final RuntimeException exception = new IllegalStateException();
		final Fn<String, Object, ? extends RuntimeException> fn = MiscUtils.tryLocales(
				FnUtils.throwing(exception), threadLocal);
		threadLocal.set(Locale.JAPANESE);
		try {
			fn.exec("id0");
			fail();
		} catch (final IllegalStateException expected) {
			assertSame(exception, expected.getCause());
		}
	}

	public void testLoadBalancerException() throws Exception {
		MiscUtils.loadBalancer(FnUtils.<Object, Fn<Object, ?, RuntimeException>>fix(null), 1);
		try {
			MiscUtils.loadBalancer(FnUtils.<Object, Fn<Object, ?, RuntimeException>>fix(null), 0);
			fail();
		} catch (final IllegalArgumentException expected) {
		}
	}
	


	private int errorThreshold = 2;
	public void testRetry() throws Exception {
		final Fn<String, String, IllegalStateException> fn = MiscUtils.retry(
				new Fn<String, String, Exception>() {
					private int count = 0;
					public String exec(final String c) throws Exception {
						if (count++ < errorThreshold) {
							throw new Exception();
						}
						final String s = c + "hona" + count;
						count = 0;
						return s;
					}
				},
				4
		);
		
		assertEquals("hogehona3", fn.exec("hoge"));
		
		errorThreshold = 4;
		try {
			fn.exec("hoge");
			fail();
		} catch(final IllegalStateException e) {
		}
	}
}
