package coin.optargs;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

public class OptArgs {
	
	private static final OptArgs EMPTY = new OptArgs(Collections.<String,Object>emptyMap());
	private static final Object NULL = new Object();
	private final Map<String,Object> args;
	
	
	public OptArgs(Map<String,Object> args) {
		this.args = args;
	}
	
	public static OptArgs empty() {
		return EMPTY;
	}
	
	public static OptArgs make(Object...args) {
		if (args.length%2!=0) {
			throw new IllegalStateException("bad args count");
		}
		Map<String,Object> map = new HashMap<String, Object>(args.length/2);
		for (int i=0; i<args.length; i+=2) {
			String name = args[i].toString();
			Object value = args[i+1];
			if (value==null)
				value = OptArgs.NULL;
			map.put(name, value);
		}
		return new OptArgs(map);
	}
	
	public boolean contains(String name, Class<?> type) {
		final Object value = this.args.get(name);
		if (value==null)
			return false;
		if (value==NULL)
			return true;
		return wrapper(type).isInstance(value);
	}
	
	public <T> T retrieve(String name, Class<? extends T> type) {
		final Object value = this.args.remove(name);
		if (value==null)
			throw new IllegalStateException("The parameter '"
					+ type.getName() + " " + name + "' is required.");
		if (value==OptArgs.NULL)
			return nullValue(type);
		return wrapper(type).cast(value);
	}
	
	public <T> T retrieve(String name, Class<? extends T> type, T defaultValue) {
		final Object value = this.args.remove(name);
		if (value==null)
			return defaultValue;
		if (value==OptArgs.NULL)
			return nullValue(type);
		return wrapper(type).cast(value);
	}
	
	public Object retrieve(String name) {
		return retrieve(name, Object.class);
	}
	
	@SuppressWarnings("unchecked")
	private <T> T nullValue(Class<T> type) {
		if (type.isPrimitive()) {
			if (type==byte.class) return (T)Byte.valueOf((byte)0);
			if (type==short.class) return (T)Short.valueOf((short)0);
			if (type==int.class) return (T)Integer.valueOf((int)0);
			if (type==long.class) return (T)Long.valueOf((long)0);
			if (type==float.class) return (T)Float.valueOf((float)0.0);
			if (type==double.class) return (T)Double.valueOf((double)0.0);
			if (type==char.class) return (T)Character.valueOf((char)0);
			if (type==boolean.class) return (T)Boolean.FALSE;
		}
		return null;
	}
	
	@SuppressWarnings("unchecked")
	private <T> Class<T> wrapper(Class<T> type) {
		if (type.isPrimitive()) {
			if (type==byte.class) return (Class<T>)Byte.class;
			if (type==short.class) return (Class<T>)Short.class;
			if (type==int.class) return (Class<T>)Integer.class;
			if (type==long.class) return (Class<T>)Long.class;
			if (type==float.class) return (Class<T>)Float.class;
			if (type==double.class) return (Class<T>)Double.class;
			if (type==char.class) return (Class<T>)Character.class;
			if (type==boolean.class) return (Class<T>)Boolean.class;
			if (type==void.class) return (Class<T>)Void.class;
		}
		return type;
	}
	
	public Iterable<String> names() {
		return this.args.keySet();
	}
	
	public int available() {
		return this.args.size();
	}
	
	@Override
	public String toString() {
		return this.args.toString();
	}
}
