/*
 *  Copyright 2008 biaoping.yin
 *
 *  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 org.frameworkset.web.servlet.handler.annotations;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.jsp.PageContext;

import org.frameworkset.util.ClassUtils;
import org.frameworkset.util.annotations.HandlerMapping;
import org.frameworkset.util.annotations.MethodInfo;
import org.frameworkset.util.annotations.ModelAttribute;
import org.frameworkset.util.annotations.SessionAttributes;
import org.frameworkset.web.servlet.ModelAndView;
import org.frameworkset.web.servlet.handler.HandlerUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;



/**
 * <p>Title: HandlerMethodResolver.java</p> 
 * <p>Description: </p>
 * <p>bboss workgroup</p>
 * <p>Copyright (c) 2008</p>
 * @Date 2010-10-24
 * @author biaoping.yin
 * @version 1.0
 */
public class HandlerMethodResolver {
	
	private  Set<MethodInfo> handlerMethods = new LinkedHashSet<MethodInfo>();
	/** Methods, keyed by exception class */
	private  Map<Class, Method> exceptionHandlerMap = new HashMap<Class, Method>();
	private final static Logger log = LoggerFactory.getLogger(HandlerMethodResolver.class);

	private final Set<Method> initBinderMethods = new LinkedHashSet<Method>();

	private final Set<Method> modelAttributeMethods = new LinkedHashSet<Method>();

	private final HandlerMapping typeLevelMapping;

	private final boolean sessionAttributesFound;

	private final Set<String> sessionAttributeNames = new HashSet<String>();

	private final Set<Class> sessionAttributeTypes = new HashSet<Class>();

	private final Set<String> actualSessionAttributeNames = Collections.synchronizedSet(new HashSet<String>(4));
	/**
	 * Is the supplied method a valid exception handler method?
	 */
	private boolean isExceptionHandlerMethod(Method method) {
		Class returnType = method.getReturnType();
		return ((ModelAndView.class.equals(returnType)
				|| Map.class.equals(returnType)
				|| String.class.equals(returnType)
				|| void.class.equals(returnType)) &&
				method.getParameterTypes().length >= 4 && HttpServletRequest.class
						.isAssignableFrom(method.getParameterTypes()[0])
						&& HttpServletResponse.class
								.isAssignableFrom(method.getParameterTypes()[1])
						&& PageContext.class
								.isAssignableFrom(method.getParameterTypes()[2]) && 
				Throwable.class.isAssignableFrom(method.getParameterTypes()[3]));
	}
	/**
	 * Registers the supplied method as an exception handler.
	 */
	private void registerExceptionHandlerMethod(Method method) {
		this.exceptionHandlerMap.put(method.getParameterTypes()[3], method);
		
		log.debug("Found exception handler method [" + method + "]");
		
	}
	
	/**
	 * Determine the exception handler method for the given exception.
	 * <p>Can return <code>null</code> if not found.
	 * @return a handler for the given exception type, or <code>null</code>
	 * @param exception the exception to handle
	 */
	protected Method getExceptionHandler(Throwable exception) {
		Class exceptionClass = exception.getClass();
		
		log.debug("Trying to find handler for exception class [" + exceptionClass.getName() + "]");
		
		Method handler = this.exceptionHandlerMap.get(exceptionClass);
		while (handler == null && !exceptionClass.equals(Throwable.class)) {
			
			log.debug("Trying to find handler for exception superclass [" + exceptionClass.getName() + "]");
			
			exceptionClass = exceptionClass.getSuperclass();
			handler = this.exceptionHandlerMap.get(exceptionClass);
		}
		return handler;
	}
	
	
	/**
	 * Create a new HandlerMethodResolver for the specified handler type.
	 * @param handlerType the handler class to introspect
	 */
	public HandlerMethodResolver(final Class<?> handlerType) {
		this.typeLevelMapping = handlerType.getAnnotation(HandlerMapping.class);
		Method[] methods = handlerType.getMethods();
		
		for(Method method:methods) {
			if (isExceptionHandlerMethod(method)) {
				registerExceptionHandlerMethod(method);
			}
			else	if (HandlerUtils.isHandlerMethod(handlerType,method)) {
					handlerMethods.add(new MethodInfo(ClassUtils.getMostSpecificMethod(method, handlerType),typeLevelMapping ));
				}
//				else if (method.isAnnotationPresent(InitBinder.class)) {
//					initBinderMethods.add(ClassUtils.getMostSpecificMethod(method, handlerType));
//				}
				else if (method.isAnnotationPresent(ModelAttribute.class)) {
					modelAttributeMethods.add(ClassUtils.getMostSpecificMethod(method, handlerType));
				}
			}
		
		
		SessionAttributes sessionAttributes = handlerType.getAnnotation(SessionAttributes.class);
		this.sessionAttributesFound = (sessionAttributes != null);
		if (this.sessionAttributesFound) {
			this.sessionAttributeNames.addAll(Arrays.asList(sessionAttributes.value()));
			this.sessionAttributeTypes.addAll(Arrays.asList(sessionAttributes.types()));
		}
	}
	
	/**
	 * Create a new HandlerMethodResolver for the specified handler type.
	 * @param handlerType the handler class to introspect
	 */
	public HandlerMethodResolver(final Class<?> handlerType,final String baseurls[]) {
		this.typeLevelMapping = handlerType.getAnnotation(HandlerMapping.class);
		Method[] methods = handlerType.getMethods();
		for(Method method:methods)
		{
				
				if (HandlerUtils.isHandlerMethod(handlerType,method)) {
					//System.out.println(method);
					handlerMethods.add(new MethodInfo(ClassUtils.getMostSpecificMethod(method, handlerType),baseurls));
				}
//				else if (method.isAnnotationPresent(InitBinder.class)) {
//					initBinderMethods.add(ClassUtils.getMostSpecificMethod(method, handlerType));
//				}
				else if (method.isAnnotationPresent(ModelAttribute.class)) {
					modelAttributeMethods.add(ClassUtils.getMostSpecificMethod(method, handlerType));
				}
			}
		
		SessionAttributes sessionAttributes = handlerType.getAnnotation(SessionAttributes.class);
		this.sessionAttributesFound = (sessionAttributes != null);
		if (this.sessionAttributesFound) {
			this.sessionAttributeNames.addAll(Arrays.asList(sessionAttributes.value()));
			this.sessionAttributeTypes.addAll(Arrays.asList(sessionAttributes.types()));
		}
	}


	public final boolean hasHandlerMethods() {
		return !this.handlerMethods.isEmpty();
	}

	public final Set<MethodInfo> getHandlerMethods() {
		return this.handlerMethods;
	}

	public final Set<Method> getInitBinderMethods() {
		return this.initBinderMethods;
	}

	public final Set<Method> getModelAttributeMethods() {
		return this.modelAttributeMethods;
	}

	public boolean hasTypeLevelMapping() {
		return (this.typeLevelMapping != null);
	}

	public HandlerMapping getTypeLevelMapping() {
		return this.typeLevelMapping;
	}

	public boolean hasSessionAttributes() {
		return this.sessionAttributesFound;
	}

	public boolean isSessionAttribute(String attrName, Class attrType) {
		if (this.sessionAttributeNames.contains(attrName) || this.sessionAttributeTypes.contains(attrType)) {
			this.actualSessionAttributeNames.add(attrName);
			return true;
		}
		else {
			return false;
		}
	}

	public Set<String> getActualSessionAttributeNames() {
		return this.actualSessionAttributeNames;
	}
	public void destroy()
	{
		if(handlerMethods != null)
		{
			handlerMethods.clear();
			handlerMethods = null;
		}
		
		if(exceptionHandlerMap != null)
		{
			exceptionHandlerMap.clear();
			exceptionHandlerMap = null;
		}
	}

}
