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

import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.text.SimpleDateFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

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

import org.frameworkset.http.converter.HttpMessageConverter;
import org.frameworkset.spi.support.validate.BindingResult;
import org.frameworkset.util.ClassUtil;
import org.frameworkset.util.ParameterUtil;
import org.frameworkset.util.ClassUtil.ClassInfo;
import org.frameworkset.util.ClassUtil.PropertieDescription;
import org.frameworkset.util.annotations.MethodData;
import org.frameworkset.web.multipart.MultipartFile;
import org.frameworkset.web.multipart.MultipartHttpServletRequest;
import org.frameworkset.web.servlet.ModelMap;
import org.frameworkset.web.servlet.handler.HandlerUtils;

import com.frameworkset.util.EditorInf;
import com.frameworkset.util.ValueObjectUtil;

/**
 * <p>Title: WebDataBinder.java</p> 
 * <p>Description: </p>
 * <p>bboss workgroup</p>
 * <p>Copyright (c) 2008</p>
 * @Date 2010-10-12
 * @author biaoping.yin
 * @version 1.0
 */
public class WebDataBinder  {//extends DataBinder {
	private Object target;

	private  String objectName;
	private Class objectType;
	private String paramName;
	
	private BindingResult bindingResult;

	/**
	 * Default prefix that field marker parameters start with, followed by the field
	 * name: e.g. "_subscribeToNewsletter" for a field "subscribeToNewsletter".
	 * <p>Such a marker parameter indicates that the field was visible, that is,
	 * existed in the form that caused the submission. If no corresponding field
	 * value parameter was found, the field will be reset. The value of the field
	 * marker parameter does not matter in this case; an arbitrary value can be used.
	 * This is particularly useful for HTML checkboxes and select options.
	 * @see #setFieldMarkerPrefix
	 */
	public static final String DEFAULT_FIELD_MARKER_PREFIX = "_";


	private String fieldMarkerPrefix = DEFAULT_FIELD_MARKER_PREFIX;

	private boolean bindEmptyMultipartFiles = true;
	
	private Collection targetContainer;
	private Map targetMapContainer;
	private String mapKeyName;
	private Class mapKeyType;
	


	/**
	 * Create a new WebDataBinder instance, with default object name.
	 * @param target the target object to bind onto (or <code>null</code>
	 * if the binder is just used to convert a plain parameter value)
	 * @see #DEFAULT_OBJECT_NAME
	 */
	public WebDataBinder(Object target) {
//		super(target);
		this( target, null);
	}
	
	public BindingResult getBindingResult()
	{
		return bindingResult;
	}

	/**
	 * Create a new WebDataBinder instance.
	 * @param target the target object to bind onto (or <code>null</code>
	 * if the binder is just used to convert a plain parameter value)
	 * @param objectName the name of the target object
	 */
	public WebDataBinder(Object target, String objectName) {
//		super(target, objectName);
		this.target = target;
		this.objectName = objectName;
	}
	
	public WebDataBinder(Collection target, String objectName,Class objectType,String paramname) {
//		super(target, objectName);
		this.targetContainer = target;
		this.objectName = objectName;
		this.objectType = objectType; 
		this.paramName = paramname;
	}
	public WebDataBinder(Map target, String mapKeyName,Class mapKeyType,String objectName,Class objectType) {
//		super(target, objectName);
		this.targetMapContainer = target;
		this.objectName = objectName;
		this.objectType = objectType; 
		this.mapKeyName = mapKeyName;
		this.mapKeyType = mapKeyType;
		
	}
	
	
	


	/**
	 * Specify a prefix that can be used for parameters that mark potentially
	 * empty fields, having "prefix + field" as name. Such a marker parameter is
	 * checked by existence: You can send any value for it, for example "visible".
	 * This is particularly useful for HTML checkboxes and select options.
	 * <p>Default is "_", for "_FIELD" parameters (e.g. "_subscribeToNewsletter").
	 * Set this to null if you want to turn off the empty field check completely.
	 * <p>HTML checkboxes only send a value when they're checked, so it is not
	 * possible to detect that a formerly checked box has just been unchecked,
	 * at least not with standard HTML means.
	 * <p>One way to address this is to look for a checkbox parameter value if
	 * you know that the checkbox has been visible in the form, resetting the
	 * checkbox if no value found. In Bboss web MVC, this typically happens
	 * in a custom <code>onBind</code> implementation.
	 * <p>This auto-reset mechanism addresses this deficiency, provided
	 * that a marker parameter is sent for each checkbox field, like
	 * "_subscribeToNewsletter" for a "subscribeToNewsletter" field.
	 * As the marker parameter is sent in any case, the data binder can
	 * detect an empty field and automatically reset its value.
	 * @see #DEFAULT_FIELD_MARKER_PREFIX
	 * 
	 */
	public void setFieldMarkerPrefix(String fieldMarkerPrefix) {
		this.fieldMarkerPrefix = fieldMarkerPrefix;
	}

	/**
	 * Return the prefix for parameters that mark potentially empty fields.
	 */
	public String getFieldMarkerPrefix() {
		return this.fieldMarkerPrefix;
	}

	/**
	 * Set whether to bind empty MultipartFile parameters. Default is "true".
	 * <p>Turn this off if you want to keep an already bound MultipartFile
	 * when the user resubmits the form without choosing a different file.
	 * Else, the already bound MultipartFile will be replaced by an empty
	 * MultipartFile holder.
	 * 
	 */
	public void setBindEmptyMultipartFiles(boolean bindEmptyMultipartFiles) {
		this.bindEmptyMultipartFiles = bindEmptyMultipartFiles;
	}

	/**
	 * Return whether to bind empty MultipartFile parameters.
	 */
	public boolean isBindEmptyMultipartFiles() {
		return this.bindEmptyMultipartFiles;
	}

	private boolean isCollection()
	{
		return this.targetContainer != null || this.targetMapContainer != null;
	}
	/**
	 * This implementation performs a field marker check
	 * before delegating to the superclass binding process.
	 * @throws Exception 
	 * @see #checkFieldMarkers
	 */
	protected void doBind(HttpServletRequest request,HttpServletResponse response,PageContext pageContext,
			MethodData handlerMethod,ModelMap model,HttpMessageConverter[] messageConverters) throws Exception {
//		checkFieldMarkers(mpvs);
		if(!this.isCollection())
			createTransferObject( request, response, pageContext,
					 handlerMethod, model,this.getTarget(),messageConverters);
		else
		{
			Object target = this.getTarget();
			if(target != null) //集合指定泛型类型并且泛型类型为po对象
			{
				createTransferObject( request, response, pageContext,
						 handlerMethod, model,target,messageConverters);
			}
			else //集合指定泛型类型并且泛型类型为基础对象，或者集合没有指定泛型类型
			{
				createTransferObject(
						 request, response, pageContext,
						 handlerMethod, model, messageConverters);
			}
//			BeanInfo beanInfo = null;
//			try {
//				beanInfo = Introspector.getBeanInfo(this.objectType);
//			} catch (Exception e) {
//				model.getErrors().reject("createTransferObject.getBeanInfo.error",objectType.getCanonicalName() + ":"+e.getMessage());
////				throw new PropertyAccessException(new PropertyChangeEvent(whichToVO, "",
////					     null, null),"获取bean 信息失败",e);
//				return ;
//			}
			//第一个的时候，计算target个数
			//list需要区分从attribute中获取list和set的情况，这种情况不需要进行自动数据绑定
			//到这里的话就是需要数据绑定的对象集合,对象集合的数据目前考虑只从request中获取
			//其他
//			int bcount = createTransferObject( request, response, pageContext,
//					 handlerMethod, model,this.getTarget(),messageConverters);
			
		}
	}
	
	
	public static class CallHolder
	{
		/**保持请求数据，避免重复获取*/
		private Map<String,Object> datas = new HashMap<String,Object>();
		
		
		private int parameterCounts = -1;
		private boolean isCollection;
		
		
		/**和当前记录相关的几个参数定义开始*/
		private int position = 0;
//		private boolean required = false;
//		private EditorInf editor = null;
		private Map<String,Boolean> isarray = new HashMap<String,Boolean>();
		private Map<String,Boolean> isrequired = new HashMap<String,Boolean>();
		private Map<String,EditorInf> editors = new HashMap<String,EditorInf>();
		/**
		 * 记录属性的日期格式，如果指定就有，没有指定就为null；
		 */
		private Map<String,SimpleDateFormat> dateformats = new HashMap<String,SimpleDateFormat>();
		private Map<String,Object> defaultValues = new HashMap<String,Object>();
		
		
		public boolean needAddData()
		{
			return this.isCollection && this.parameterCounts > 1 && this.getPosition() == 0;
		}
		
		
//		/**和当前记录相关的几个参数定义结束*/
//		
//		public void initisarray()
//		{
//			this.isarray = new boolean[this.getCounts()] ;
//		}
		
		public int increamentPosition()
		{
			this.position ++;
			return position;
		}
		
		public boolean isArray(String name)
		{
			boolean isarray = this.isarray.get(name) != null && this.isarray.get(name) == true;
			return isarray;
		}
		public Object[] getParameterValues(String name)
		{
			return (Object[])this.datas.get(name);
		}
		public void setCounts(int count)
		{
			if( count > parameterCounts)
				this.parameterCounts = count;
		}
		public void addData(String name,Object value,EditorInf editor,boolean required)
		{
			this.datas.put(name, value);
			this.isarray.put(name, false);
			isrequired.put(name, required);
			editors.put(name, editor);
		}
		
		public void addData(String name,Object value,EditorInf editor,boolean required,Object defaultValue)
		{
			this.datas.put(name, value);
			this.isarray.put(name, false);
			isrequired.put(name, required);
			editors.put(name, editor);
			this.defaultValues.put(name, defaultValue);
		}
		
		public void addData(String name,Object value)
		{
			this.datas.put(name, value);
			this.isarray.put(name, false);
			isrequired.put(name, false);
			editors.put(name, null);
		}
		public void addData(String name,String[] value,boolean isarray,EditorInf editor,boolean required)
		{
			this.datas.put(name, value);
			this.isarray.put(name, isarray);
			isrequired.put(name, required);
			editors.put(name, editor);
		}
		
		
		public void addData(String name,MultipartFile[] value,boolean isarray,EditorInf editor,boolean required)
		{
			this.datas.put(name, value);
			this.isarray.put(name, isarray);
			isrequired.put(name, required);
			editors.put(name, editor);
		}
		
		public void addData(String name,String[] value,boolean isarray,EditorInf editor,boolean required,Object defaultValue,String dateformat)
		{
			this.datas.put(name, value);
			this.isarray.put(name, isarray);
			isrequired.put(name, required);
			editors.put(name, editor);
			if(dateformat != null)
			{
				SimpleDateFormat df = new SimpleDateFormat(dateformat); 
				this.dateformats.put(name, df);
			}
			this.defaultValues.put(name, defaultValue);
		}
		
		
		public Object getData(String name)
		{
			Object value = null;
			
			if(this.isArray(name))
			{
				Object[] values = this.getParameterValues(name);
				try
				{
					if(values == null || this.getPosition() >= values.length)
						value = null;
					else
						value = this.getParameterValues(name)[this.getPosition()];
				}
				catch(Exception e)
				{
					e.printStackTrace();
				}
				Object defaultValue = this.defaultValues.get(name);
				if(value == null && defaultValue != null)
					value = defaultValue;
					
			}
			else
				value = this.datas.get(name);
			return value;
		}
		public int getCounts()
		{
			return this.parameterCounts;
		}
		
		public boolean isCollection()
		{
			return this.isCollection;
		}
		
		
		
		public boolean lasted()
		{
			return this.getPosition() >= this.getCounts();
		}
		public int getPosition() {
			return position;
		}

		/**
		 * @return the required
		 */
		public boolean isRequired(String name) {
			return this.isrequired.get(name) != null && this.isrequired.get(name) ;
		}

		/**
		 * @return the editor
		 */
		public EditorInf getEditor(String name) {
			return this.editors.get(name);
		}
		
		/**
		 * @return the dateformat
		 */
		public SimpleDateFormat getDateformat(String name) {
			return this.dateformats.get(name);
		}

		
		
	}
	
	
	/**
	 * added by biaoping.yin 2005.8.13
	 * 集合对应的泛型是基础数据类型/枚举类型/附件类型 或者没有指定类型
	 */
	public  void createTransferObject(
			HttpServletRequest request,HttpServletResponse response,PageContext pageContext,
			MethodData handlerMethod,ModelMap model,HttpMessageConverter[] messageConverters)
	{		
//		BeanInfo beanInfo = null;
//		try {
//			beanInfo = Introspector.getBeanInfo(whichToVO.getClass());
//			
//		} catch (Exception e) {
//			model.getErrors().reject("createTransferObject.getBeanInfo.error",whichToVO.getClass().getCanonicalName() + ":"+e.getMessage());
////			throw new PropertyAccessException(new PropertyChangeEvent(whichToVO, "",
////				     null, null),"获取bean 信息失败",e);
//			return ;
//		} 
//		ClassInfo beanInfo = ClassUtil.getClassInfo(whichToVO.getClass());
		CallHolder holder = new CallHolder();
		holder.isCollection =  true;
		

		if(holder.isCollection)//集合类型（List,Map）,如果没有数据记录，则直接返回，修复没有数据情况下返回一条空记录的问题
		{
		
				
				if(this.objectType == null)
				{
					String[] values = request.getParameterValues(this.paramName);
					
					if(values != null && values.length > 0)
					{
						for(int i = 0; i < values.length; i ++)
						{
							this.targetContainer.add(values[i]);
						}
					}
				}
				else if(HandlerUtils.isMultipartFile(objectType))
				
				{
					MultipartFile[] values = ((MultipartHttpServletRequest)request).getFiles(paramName);
					if(values != null && values.length > 0)
					{
						for(int i = 0; i < values.length; i ++)
						{
							this.targetContainer.add(values[i]);
						}
					}
				}
				else
				{
					String[] values = request.getParameterValues(this.paramName);
					if(values != null && values.length > 0)
						 ValueObjectUtil.typeCastCollection(values,targetContainer,
								this.objectType);
				}
				
				
				
			}
			
			
		
		



		
		
	}
	/**
	 * added by biaoping.yin 2005.8.13
	 * 将map中包含的属性值复制到对象中,对应属性的名称和类型必须一致
	 * @param completeVO 有属性值的map对象
	 * @param whichToVO 空对象
	 * @param validators 
	 * @return Object
	 */
	public  void createTransferObject(
			HttpServletRequest request,HttpServletResponse response,PageContext pageContext,
			MethodData handlerMethod,ModelMap model,
		    Object whichToVO,HttpMessageConverter[] messageConverters)
	{		
//		BeanInfo beanInfo = null;
//		try {
//			beanInfo = Introspector.getBeanInfo(whichToVO.getClass());
//			
//		} catch (Exception e) {
//			model.getErrors().reject("createTransferObject.getBeanInfo.error",whichToVO.getClass().getCanonicalName() + ":"+e.getMessage());
////			throw new PropertyAccessException(new PropertyChangeEvent(whichToVO, "",
////				     null, null),"获取bean 信息失败",e);
//			return ;
//		} 
		ClassInfo beanInfo = ClassUtil.getClassInfo(whichToVO.getClass());
		CallHolder holder = new CallHolder();
		holder.isCollection =  this.isCollection();
		List<PropertieDescription> attributes = beanInfo.getPropertyDescriptors();		
		Object mapKey = null;
		if(holder.isCollection)//集合类型（List,Map）,如果没有数据记录，则直接返回，修复没有数据情况下返回一条空记录的问题
		{
			boolean hasdata = false;
			PropertieDescription property = null;
			for(int in = 0; attributes != null && in < attributes.size(); in ++)
			{
				property = attributes.get(in);
				if(!property.canwrite())
				{
					continue;
				}
				
				String name = ParameterUtil.getAssertHasdataParameterName(property, property.getName(), request, 0);
				
				Class type = property.getPropertyType();
				if(!HandlerUtils.isMultipartFile(type))
				{
					String[] values = request.getParameterValues(name);
					
					if(values != null && values.length > 0)
					{
						hasdata = true;
						break;
					}
				}
				else
				{
					if(!HandlerUtils.isIgnoreFieldNameMultipartFile(type))
					{
						if(request instanceof MultipartHttpServletRequest)
						{
							MultipartFile[] values = ((MultipartHttpServletRequest)request).getFiles(name);
							if(values != null && values.length > 0)
							{
								hasdata = true;
								break;
							}
						}
					}
					else
					{
						if(request instanceof MultipartHttpServletRequest)
						{
							MultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest)request;
							MultipartFile[] values = multipartRequest.getFirstFieldFiles();
							if(values != null && values.length > 0)
							{
								hasdata = true;
								break;
							}
//							Iterator<String> filenames = multipartRequest.getFileNames();
//							if(filenames == null)
//								break;
//							while(filenames.hasNext())
//							{
//								MultipartFile[] values = multipartRequest.getFiles(filenames.next());
//								
//								if(values != null && values.length > 0)
//								{
//									hasdata = true;
//									break;
//								}
//							}
						}
					}
					
				}
				
			}
			if(!hasdata)
				return;
			
		}
		do
		{
			if(holder.getPosition() > 0)
				try {
					whichToVO = HandlerUtils.newCommandObject(objectType);
				} catch (Exception e1) {
					e1.printStackTrace();
					break;
				}
			
			
			for(int in = 0; attributes != null && in < attributes.size(); in ++)
			{
				PropertieDescription property = attributes.get(in);
				if(!property.canwrite() || property.getIgnoreBind() != null)
				{
					continue;
				}
//				Method writeMethod = property.getWriteMethod();
//				if(writeMethod == null)
//					continue;
				
				Object value = null;
				try {

					value = HandlerUtils.buildPropertyValue(property, request, response, pageContext, 
							handlerMethod, model, messageConverters, holder,whichToVO.getClass());
					if(this.mapKeyName != null && this.mapKeyName.equals(property.getName()))//如果是map对象绑定，则需要设置map key的值
						mapKey = ValueObjectUtil.typeCast(value, this.mapKeyType);
//					writeMethod.invoke(whichToVO, new Object[]{value});
					property.setValue(whichToVO, value);
					
				} catch (IllegalArgumentException e) {
					//rejectValue(String field, String errorCode, String[] rejectvalue,Class fieldtype,String defaultMessage);
//						model.getErrors().rejectValue(property.getName(), "buildPropertyValue.error",e.getMessage());
					model.getErrors().rejectValue(property.getName(), "buildPropertyValue.error", String.valueOf(value),property.getPropertyType(),null);
//						return ValueObjectUtil.getDefaultValue(property.getPropertyType());
//						throw new PropertyAccessException(new PropertyChangeEvent(whichToVO, property.getName(),
//							     null,null),"设置属性失败",e);
				} catch (IllegalAccessException e) {
					
//						throw new PropertyAccessException(new PropertyChangeEvent(whichToVO, property.getName(),
//							     null, null),"设置属性失败",e);
//						model.getErrors().rejectValue(property.getName(), "buildPropertyValue.error",e.getMessage());
					model.getErrors().rejectValue(property.getName(), "buildPropertyValue.error", String.valueOf(value),property.getPropertyType(),null);
				} catch (InvocationTargetException e) {
					
//						throw new PropertyAccessException(new PropertyChangeEvent(whichToVO, property.getName(),
//							     null, null),"设置属性失败",e);
//						model.getErrors().rejectValue(property.getName(), "buildPropertyValue.error",e.getMessage());
					model.getErrors().rejectValue(property.getName(), "buildPropertyValue.error", String.valueOf(value),property.getPropertyType(),null);
				} catch (Exception e) {
					e.printStackTrace();
//						// TODO Auto-generated catch block
//						e.printStackTrace();
//						model.getErrors().rejectValue(property.getName(), "buildPropertyValue.error",e.getMessage());
					model.getErrors().rejectValue(property.getName(), "buildPropertyValue.error", String.valueOf(value),property.getPropertyType(),null);
				}
			}
			if(holder.isCollection())
			{
				if(targetContainer != null )
					targetContainer.add(whichToVO);
				else if(mapKey != null)
					this.targetMapContainer.put(mapKey, whichToVO);
				holder.increamentPosition();
			}
		}while(this.isCollection() && !holder.lasted() );


		
		
	}

	/**
	 * Check the given property values for field markers,
	 * i.e. for fields that start with the field marker prefix.
	 * <p>The existence of a field marker indicates that the specified
	 * field existed in the form. If the property values do not contain
	 * a corresponding field value, the field will be considered as empty
	 * and will be reset appropriately.
	 * @param mpvs the property values to be bound (can be modified)
	 * @see #getFieldMarkerPrefix
	 * @see #getEmptyValue(String, Class)
	 */
//	protected void checkFieldMarkers(Map mpvs) {
//		if (getFieldMarkerPrefix() != null) {
//			String fieldMarkerPrefix = getFieldMarkerPrefix();
//			
//			if(mpvs == null || mpvs.size() <= 0)
//				return ;
//			Iterator<Map.Entry<String,Object>> its = mpvs.entrySet().iterator();
//			while(its.hasNext()){
//				Map.Entry<String,Object> pv = its.next();
//				if (pv.getKey().startsWith(fieldMarkerPrefix)) {
//					String field = pv.getKey().substring(fieldMarkerPrefix.length());
//					if (!mpvs.containsKey(field)) {
//						Class fieldType = getPropertyAccessor().getPropertyType(field);
//						mpvs.put(field, getEmptyValue(field, fieldType));
//					}
//					mpvs.remove(pv.getKey());
//				}
//			}
//		}
//	}

	/**
	 * Determine an empty value for the specified field.
	 * <p>Default implementation returns <code>Boolean.FALSE</code>
	 * for boolean fields and an empty array of array types.
	 * Else, <code>null</code> is used as default.
	 * @param field the name of the field
	 * @param fieldType the type of the field
	 * @return the empty value (for most fields: null)
	 */
	protected Object getEmptyValue(String field, Class fieldType) {
		if (fieldType != null && boolean.class.equals(fieldType) || Boolean.class.equals(fieldType)) {
			// Special handling of boolean property.
			return Boolean.FALSE;
		}
		else if (fieldType != null && fieldType.isArray()) {
			// Special handling of array property.
			return Array.newInstance(fieldType.getComponentType(), 0);
		}
		else {
			// Default value: try null.
			return null;
		}
	}


	/**
	 * Bind the multipart files contained in the given request, if any
	 * (in case of a multipart request).
	 * <p>Multipart files will only be added to the property values if they
	 * are not empty or if we're configured to bind empty multipart files too.
	 * @param multipartFiles Map of field name String to MultipartFile object
	 * @param mpvs the property values to be bound (can be modified)
	 * 
	 * @see #setBindEmptyMultipartFiles
	 */
	protected void bindMultipartFiles(Map multipartFiles, Map mpvs) {
		for (Iterator it = multipartFiles.entrySet().iterator(); it.hasNext();) {
			Map.Entry entry = (Map.Entry) it.next();
			String key = (String) entry.getKey();
			MultipartFile value = (MultipartFile) entry.getValue();
			if (isBindEmptyMultipartFiles() || !value.isEmpty()) {
				mpvs.put(key, value);
			}
		}
	}

	private Object getTarget() throws Exception {
		if(this.targetContainer == null && this.targetMapContainer == null)
			return target;
		else if(objectType != null )//应该没有问题
		{
			if(!ValueObjectUtil.isPrimaryType(objectType) 
					&& !HandlerUtils.isMultipartFile(objectType))
				return HandlerUtils.newCommandObject(objectType);
			else //集合对应的泛型是基础数据类型/枚举类型/附件类型
			{
				return null;
			}
		}
		else //是集合但是集合没有指定泛型
		{
			return null;
		}
	}

	public void setBindingResult(BindingResult bindingResult) {
		this.bindingResult = bindingResult;
	}

}
