/*
 * 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.utils;

import java.lang.reflect.Array;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * 静的メソッドの集まり。
 * @author nakamura
 *
 */
public class UtilsConstants {

	private UtilsConstants(){
	}
	
	/**
	 * 委譲先の{@link #get(Object)}の結果が null の場合にデフォルト値を返す{@link Map}を返す。
	 * @param <K> キー。
	 * @param <V> 値。
	 * @param map 変換対象。
	 * @param defaultValue デフォルト値。
	 * @return 委譲先の{@link Map#get(Object)}の結果が null の場合にデフォルト値を返す{@link Map}。
	 * @throws NullPointerException map が null の場合。
	 */
	public static <K,V> Map<K,V> defaultValueMap(final Map<K,V> map, final V defaultValue){
		map.getClass();
		return new DelegationMap<K,V>(map){
			@Override public V get(final Object key){
				final V value = super.get(key);
				return (value != null)?value:defaultValue;
			}
		};
	}
	
	/**
	 * オーバライド禁止の{@link Map}を返す。
	 * @param <K> キー。
	 * @param <V> 値。
	 * @param map オーバライドを禁止する対象の{@link Map}。
	 * @return オーバライドが禁止された{@link Map}。
	 * @throws NullPointerException 引数が null の場合。
	 */
	public static <K,V> Map<K,V> unoverwritableMap(final Map<K,V> map){
		map.getClass();
		return new DelegationMap<K,V>(map){
			@Override public V put(final K key, final V value){
				if(super.containsKey(key)){
					throw new IllegalArgumentException("duplicate: " + key);
				}
				return super.put(key, value);
			}
			@Override public void putAll(final Map<? extends K,? extends V> t){
				for(final K k:t.keySet()){
					if(super.containsKey(k)){
						throw new IllegalArgumentException("duplicate: " + k);
					}
				}
				super.putAll(t);
			}
		};
	}

	/**
	 * キー・値がnull値をとることを禁止する{@link Map}を返す。
	 * @param <K> キー。
	 * @param <V> 値。
	 * @param map キー・値がnull値をとることを禁止する対象の{@link Map}。
	 * @return キー・値がnull値をとることを禁止された{@link Map}。
	 * @throws NullPointerException 引数が null の場合。
	 */
	public static <K,V> Map<K,V> notNullMap(final Map<K,V> map){
		map.getClass();
		return new NotNullMap<K,V>(map);
	}
	
	/**
	 * キーの文字数がゼロであることを禁止する{@link Map}を返す。
	 * @param <V> 値。
	 * @param map キーの文字数がゼロであることを禁止する対象の{@link Map}。
	 * @return キーの文字数がゼロであることを禁止された{@link Map}。
	 * @throws NullPointerException 引数が null の場合。
	 */
	public static <V> Map<String,V> keyNotEmptyMap(final Map<String,V> map){
		map.getClass();
		return new KeyNotEmptyMap<V>(map);
	}
	
	/**
	 * 類似の名前を同一とみなす{@link Map}を返す。
	 * 類似の名前とは、大文字区切り(先頭文字の大文字と小文字の区別なし)とアンダーバー区切り(大文字と小文字の区別なし)の相互変換の範囲とする。
	 * @param <V> 値。
	 * @param map 類似の名前を同一とみなす対象の{@link Map}。
	 * @return 類似の名前を同一とみなす{@link Map}。
	 * @throws NullPointerException 引数が null の場合。
	 */
	public static <V> Map<String,V> similarKeyMap(final Map<String,V> map){
		map.getClass();
		return new SimilarPropertyNameMap<V>(map);
	}
	
	/**
	 * 変更禁止の{@link Iterable}を返す。
	 * @param <T> 汎用型。
	 * @param iterable 変更禁止対象の{@link Iterable}。
	 * @return 変更禁止された{@link Iterable}。
	 */
	public static <T> Iterable<T> unmodifiableIterable(final Iterable<T> iterable){
		return new Iterable<T>(){
			public Iterator<T> iterator() {
				final Iterator<T> it = iterable.iterator();
				return new Iterator<T>(){
					public boolean hasNext() {
						return it.hasNext();
					}
					public T next() {
						return it.next();
					}
					public void remove() {
						throw new UnsupportedOperationException();
					}
				};
			}
		};
	}
	
	/**
	 * オブジェクトを{@link Iterable}に変換する。
	 * 変換元が配列なら{@link Iterable}に変換する。
	 * 変換元が{@link Iterable}ならそのまま返す。
	 * 上記以外ならその値ひとつの{@link Iterable}を返す。
	 * @param value 変換元。
	 * @return 変換された{@link Iterable}。
	 */
	public static Iterable toIterable(final Object value){
		if(value instanceof Iterable){
			return (Iterable)value;
		}
		return toListPrivate(value);
	}
	
	/**
	 * オブジェクトを{@link Collection}に変換する。
	 * 変換元が配列なら{@link Collection}に変換する。
	 * 変換元が{@link Collection}ならそのまま返す。
	 * 上記以外ならその値ひとつの{@link Collection}を返す。
	 * @param value 変換元。
	 * @return 変換された{@link Collection}。
	 */
	public static Collection toCollection(final Object value){
		if(value instanceof Collection){
			return (Collection)value;
		}
		return toListPrivate(value);
	}
	
	/**
	 * オブジェクトを{@link List}に変換する。
	 * 変換元が配列なら{@link List}に変換する。
	 * 変換元が{@link List}ならそのまま返す。
	 * 上記以外ならその値ひとつの{@link List}を返す。
	 * @param value 変換元。
	 * @return 変換された{@link List}。
	 */
	public static List toList(final Object value){
		if(value instanceof List){
			return (List)value;
		}
		return toListPrivate(value);
	}
	
	private static List<Object> toListPrivate(final Object value){
		if(value != null && value.getClass().isArray()){
			if(value.getClass().getComponentType().isPrimitive()){
				return new AbstractList<Object>(){
					@Override
					public Object get(final int index) {
						return Array.get(value, index);
					}
					@Override
					public int size() {
						return Array.getLength(value);
					}
					@Override
					public Object set(final int index, final Object after){
						final Object before = get(index);
						Array.set(value, index, after);
						return before;
					}
				};
			}else{
				return Arrays.asList((Object[])value);
			}
		}
		return Collections.singletonList(value);
	}
	
	/**
	 * {@link Throwable#getCause()}をたどってエラーメッセージの一覧に変換する。
	 * 中間の{@link Throwable}の場合、
	 * メッセージが null でないかつメッセージが原因の{@link Throwable#toString()}と同一でない場合にメッセージとして追加する。
	 * 終端の{@link Throwable}の場合、
	 * メッセージが null の場合は{@link Throwable#toString()}をメッセージとして追加し、
	 * メッセージが null でない場合はそれをメッセージとして追加する。
	 * @param t 基点。
	 * @return メッセージの一覧。
	 * @throws NullPointerException 引数が null の場合。
	 */
	public static List<String> toMessageList(Throwable t){
		final List<String> list = new ArrayList<String>();
		for(; t != null; t = t.getCause()){
			final String s = t.getLocalizedMessage();
			if(t.getCause() == null){
				if(s != null){
					list.add(s);
				}else{
					list.add(t.toString());
				}
			}else{
				if(s != null && !t.getCause().toString().equals(s)){
					list.add(s);
				}
			}
		}
		return list;
	}
}
