/*
 * Copyright (c) 2009 OrangeSignal.com All rights reserved.
 * 
 * これは Apache ライセンス Version 2.0 (以下、このライセンスと記述) に
 * 従っています。このライセンスに準拠する場合以外、このファイルを使用
 * してはなりません。このライセンスのコピーは以下から入手できます。
 * 
 * http://www.apache.org/licenses/LICENSE-2.0.txt
 * 
 * 適用可能な法律がある、あるいは文書によって明記されている場合を除き、
 * このライセンスの下で配布されているソフトウェアは、明示的であるか暗黙の
 * うちであるかを問わず、「保証やあらゆる種類の条件を含んでおらず」、
 * 「あるがまま」の状態で提供されるものとします。
 * このライセンスが適用される特定の許諾と制限については、このライセンス
 * を参照してください。
 */

package jp.sf.orangesignal.ta.util;

import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParseException;
import java.text.ParsePosition;
import java.util.Currency;
import java.util.Locale;

/**
 * <p>数値/通貨のフォーマットと解析をロケールを考慮して行うユーティリティを提供します。</p>
 * <p>このクラスは、{@link DecimalFormat} の使用を簡素化する為のユーティリティ機能を提供します。</p>
 * 
 * @author 杉澤 浩二
 * @since 2.1
 */
public abstract class NumberFormatUtils {

	/**
	 * <p>指定された複数の数値/通貨書式文字列を順番に使用して、指定された数値/通貨文字列を解析します。</p>
	 * 
	 * @param source 解析する数値/通貨文字列
	 * @param patterns 数値/通貨書式文字列群
	 * @throws IllegalArgumentException  数値/通貨文字列または数値/通貨書式文字列群、通貨コード群に不正な値が指定された場合
	 * @throws ParseException 指定された数値/通貨文字列が解析できない場合
	 * @see #parse(String, String[], Locale, String[])
	 */
	public static Number parse(final String source, final String[] patterns) throws ParseException {
		return parse(source, patterns, null, null);
	}

	/**
	 * <p>指定された複数の数値/通貨書式文字列を順番に使用して、指定された数値/通貨文字列を解析します。</p>
	 * 
	 * @param source 解析する数値/通貨文字列
	 * @param patterns 数値/通貨書式文字列群
	 * @param locale 解析で使用する地域情報
	 * @return 解析された数値/通貨
	 * @throws IllegalArgumentException  数値/通貨文字列または数値/通貨書式文字列群、通貨コード群に不正な値が指定された場合
	 * @throws ParseException 指定された数値/通貨文字列が解析できない場合
	 * @see #parse(String, String[], Locale, String[])
	 */
	public static Number parse(final String source, final String[] patterns, final Locale locale) throws ParseException {
		return parse(source, patterns, locale, null);
	}

	/**
	 * <p>指定された複数の数値/通貨書式文字列と通貨コードを順番に使用して、指定された数値/通貨文字列を解析します。</p>
	 * <p>このメソッドでは Java 仮想マシンの言語や地域などのホスト環境に依存せずに、数値/通貨の解析を行うことが可能です。</p>
	 * 
	 * <pre>
	 * 以下は Java 仮想マシンの言語や地域などのホスト環境に依存せずに、日本の数値形式で表現されているアメリカ・ドル通貨文字列を解析する場合の例です。<br />
	 * Number number = NumberFormatUtils.parse("USD 123,456,789.10", new String[]{ "\u00A4\u00A4 #,##0.00" }, Locale.JAPAN, new String[]{ "USD" });
	 * </pre>
	 * 
	 * @param source 解析する数値/通貨文字列
	 * @param patterns 数値/通貨書式文字列群
	 * @param locale 解析で使用する地域情報
	 * @param currencyCodes 通貨コード (ISO 4217 コード) 群
	 * @return 解析された数値/通貨
	 * @throws IllegalArgumentException  数値/通貨文字列または数値/通貨書式文字列群、通貨コード群に不正な値が指定された場合
	 * @throws ParseException 指定された数値/通貨文字列が解析できない場合
	 */
	public static Number parse(final String source, final String[] patterns, final Locale locale, final String[] currencyCodes) throws ParseException {
		Assert.notNull(source, "Source must not be null");
		Assert.notNull(patterns, "Patterns must not be null");
		Assert.noNullElements(patterns, "Patterns must not contain null elements");
		Assert.noNullElements(currencyCodes, "Currency codes must not contain null elements");

		DecimalFormat parser = null;
		final ParsePosition pos = new ParsePosition(0);
		for (final String pattern : patterns) {
			if (parser == null) {
				if (locale != null)
					parser = new DecimalFormat(pattern, DecimalFormatSymbols.getInstance(locale));
				else
					parser = new DecimalFormat(pattern);
			} else {
				parser.applyPattern(pattern);
			}

			if (ArrayUtils.isEmpty(currencyCodes)) {
				pos.setIndex(0);
				final Number number = parser.parse(source, pos);
				if (number != null /* && pos.getIndex() == source.length() */)
					return number;
			} else {
				for (final String currencyCode : currencyCodes) {
					parser.setCurrency(Currency.getInstance(currencyCode));
					pos.setIndex(0);
					final Number number = parser.parse(source, pos);
					if (number != null /* && pos.getIndex() == source.length() */)
						return number;
				}
			}
		}
		throw new ParseException("Unable to parse the number: " + source, -1);
	}

	/**
	 * <p>指定された数値をフォーマットして文字列を作成します。</p>
	 * 
	 * @param number フォーマットする数値
	 * @param pattern 数値/通貨書式文字列
	 * @return フォーマットされた数値文字列
	 * @throws IllegalArgumentException  数値または数値/通貨書式文字列に <code>null</code> が指定された場合。
	 * または数値/通貨書式文字列に指定されたパターンが正しくない場合。
	 * または指定された数値をフォーマットできない場合。
	 * @see #format(Number, String, Locale, String)
	 */
	public static String format(final Number number, final String pattern) {
		return format(number, pattern, null, null);
	}

	/**
	 * <p>指定された数値をフォーマットして文字列を作成します。</p>
	 * 
	 * @param number フォーマットする数値
	 * @param pattern 数値/通貨書式文字列
	 * @param locale フォーマットで使用する地域情報
	 * @return フォーマットされた数値文字列
	 * @throws IllegalArgumentException  数値または数値/通貨書式文字列に <code>null</code> が指定された場合。
	 * または数値/通貨書式文字列に指定されたパターンが正しくない場合。
	 * または指定された数値をフォーマットできない場合。
	 * @see #format(Number, String, Locale, String)
	 */
	public static String format(final Number number, final String pattern, final Locale locale) {
		return format(number, pattern, locale, null);
	}

	/**
	 * <p>指定された数値をフォーマットして文字列を作成します。</p>
	 * <p>このメソッドでは Java 仮想マシンの言語や地域などのホスト環境に依存せずに、数値/通貨のフォーマットを行うことが可能です。</p>
	 * 
	 * <pre>
	 * 以下は Java 仮想マシンの言語や地域などのホスト環境に依存せずに、日本の数値形式でアメリカ・ドル通貨文字列へフォーマットする場合の例です。<br />
	 * String str = NumberFormatUtils.format(Double.valueOf(123456789.10D), "\u00A4\u00A4 #,##0.00", Locale.JAPAN, "USD");
	 * </pre>
	 * 
	 * @param number フォーマットする数値
	 * @param pattern 数値/通貨書式文字列
	 * @param locale フォーマットで使用する地域情報
	 * @param currencyCode 通貨コード (ISO 4217 コード)
	 * @return フォーマットされた数値文字列
	 * @throws IllegalArgumentException  数値または数値/通貨書式文字列に <code>null</code> が指定された場合。
	 * または数値/通貨書式文字列に指定されたパターンが正しくない場合。
	 * または通貨コードが、サポートされた ISO 4217 コードでない場合。
	 * または指定された数値をフォーマットできない場合。
	 */
	public static String format(final Number number, final String pattern, final Locale locale, final String currencyCode) {
		Assert.notNull(number, "Number must not be null");
		Assert.notNull(pattern, "Pattern must not be null");

		final DecimalFormat formatter;
		if (locale != null)
			formatter = new DecimalFormat(pattern, DecimalFormatSymbols.getInstance(locale));
		else
			formatter = new DecimalFormat(pattern);

		if (currencyCode != null && !currencyCode.isEmpty())
			formatter.setCurrency(Currency.getInstance(currencyCode));

		return formatter.format(number);
	}

}
