package org.phoenixctms.ctsms.web.jersey.resource;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map.Entry;

import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;

import org.andromda.spring.MethodParameterNames;
import org.phoenixctms.ctsms.util.AssociationPath;
import org.phoenixctms.ctsms.util.MethodTransfilter;

public class ArgsUriPart {

	private final static Comparator<String> PARAMETER_TO_ARGUMENT_SORTING = null;
	private boolean slurp;
	private String resource;
	private LinkedHashMap<String, Object> overrides;
	private LinkedHashMap<String, Object> defaults;
	private HashSet<String> excludePrimitiveConversion;
	private Class<?> declaringInterface;
	private ArrayList<StringConverter<?>> primitiveConversionPrecedence;
	private MethodTransfilter methodTransfilter;

	public ArgsUriPart(Class<?> declaringInterface, String resource, MethodTransfilter methodTransfilter) {
		excludePrimitiveConversion = new HashSet<String>();
		overrides = new LinkedHashMap<String, Object>();
		defaults = new LinkedHashMap<String, Object>();
		primitiveConversionPrecedence = new ArrayList<StringConverter<?>>();
		reset(declaringInterface, resource, methodTransfilter);
	}

	private Method getAnnotatedMethod(String resource) throws Exception {
		if (declaringInterface != null) {
			if (methodTransfilter != null) {
				return AssociationPath.findMethod(resource, methodTransfilter, true, declaringInterface.getMethods());
			} else {
				return AssociationPath.findMethod(resource, true, declaringInterface.getMethods());
			}
		}
		return null;
	}

	private LinkedHashMap<String, Object> getArgMap(MultivaluedMap<String, String> queryParameters) throws Exception {
		LinkedHashMap<String, Object> arguments = new LinkedHashMap<String, Object>();
		Collection<NamedParameter> namedParameters = getNamedParameters(resource, false);
		if (namedParameters.size() > 0) {
			Iterator<NamedParameter> it = namedParameters.iterator();
			while (it.hasNext()) {
				NamedParameter namedParameter = it.next();
				arguments.put(namedParameter.getName(), getArgumentValue(namedParameter.getName(), queryParameters, namedParameter.getType()));
			}
		} else if (slurp && queryParameters.keySet().size() > 0) {
			ArrayList<String> parameters = new ArrayList<String>(queryParameters.keySet());
			Collections.sort(parameters, PARAMETER_TO_ARGUMENT_SORTING);
			Iterator<String> it = parameters.iterator();
			while (it.hasNext()) { // no concurrentmodexc
				String name = it.next();
				arguments.put(name, getArgumentValue(name, queryParameters, null));
			}
		}
		return arguments;
	}

	private Object[] getArgs(MultivaluedMap<String, String> queryParameters) throws Exception {
		return getArgMap(queryParameters).values().toArray();
	}

	public Object[] getArgs(UriInfo uriInfo) throws Exception {
		return getArgs(uriInfo.getQueryParameters(true));
	}

	private Object getArgumentValue(String parameterName, MultivaluedMap<String, String> queryParameters, Type type) throws Exception {
		if (overrides.containsKey(parameterName)) {
			return overrides.get(parameterName);
		} else if (queryParameters.containsKey(parameterName)) {
			String value = ResourceUtils.popQueryParamValue(parameterName, queryParameters);
			if (type != null) {
				return StringConverter.getConverter(type).convert(value);
			} else {
				if (excludePrimitiveConversion.contains(parameterName)) {
					return value;
				}
				return StringConverter.convert(value, primitiveConversionPrecedence);
			}
		} else if (defaults.containsKey(parameterName)) {
			return defaults.get(parameterName);
		} else {
			return null;
		}
	}

	public HashSet<String> getExcludePrimitiveConversion() {
		return excludePrimitiveConversion;
	}

	public MethodTransfilter getMethodTransfilter() {
		return methodTransfilter;
	}

	public LinkedHashSet<NamedParameter> getNamedParameters(String resource, boolean excludeOverriden) throws Exception {
		LinkedHashMap<String, NamedParameter> namedParameters = new LinkedHashMap<String, NamedParameter>();
		Method method = getAnnotatedMethod(resource);
		if (method != null) {
			MethodParameterNames annotation = method.getAnnotation(MethodParameterNames.class);
			String[] parameterNames = null;
			if (annotation != null) {
				parameterNames = annotation.value();
			}
			Class<?>[] parameterTypes = method.getParameterTypes();
			if (parameterNames != null && parameterTypes != null) {
				for (int i = 0; i < parameterNames.length; i++) {
					namedParameters.put(parameterNames[i], new NamedParameter(parameterNames[i], parameterTypes[i]));
				}
			}
		}
		Iterator<Entry<String, Object>> it = defaults.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String, Object> defaultParameter = it.next();
			if (!namedParameters.containsKey(defaultParameter.getKey())) {
				namedParameters.put(defaultParameter.getKey(), new NamedParameter(defaultParameter.getKey(), defaultParameter.getValue().getClass()));
			}
		}
		it = overrides.entrySet().iterator();
		while (it.hasNext()) {
			Entry<String, Object> overrideParameter = it.next();
			namedParameters.put(overrideParameter.getKey(), new NamedParameter(overrideParameter.getKey(), overrideParameter.getValue().getClass()));
		}
		if (excludeOverriden) {
			it = overrides.entrySet().iterator();
			while (it.hasNext()) {
				namedParameters.remove(it.next().getKey());
			}
		}
		return new LinkedHashSet<NamedParameter>(namedParameters.values());
	}

	public LinkedHashMap<String, Object> getOverrides() {
		return overrides;
	}

	public void reset(Class<?> declaringInterface, String resource, MethodTransfilter methodTransfilter) {
		excludePrimitiveConversion.clear();
		overrides.clear();
		defaults.clear();
		primitiveConversionPrecedence.clear();
		this.methodTransfilter = methodTransfilter;
		this.declaringInterface = declaringInterface;
		this.resource = resource;
		slurp = true;
	}

	public Object shiftParameters(MultivaluedMap<String, String> queryParameters) throws Exception {
		return getArgs(queryParameters);
	}
}