package org.nutz.plugins.validation;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.nutz.el.El;
import org.nutz.lang.Lang;
import org.nutz.lang.Mirror;
import org.nutz.lang.Strings;
import org.nutz.lang.reflect.FastClassFactory;
import org.nutz.lang.reflect.FastMethod;
import org.nutz.lang.util.Context;

/**
 * 辅助验证的工具类
 * <p>
 * 该类为虚类，不能实例化，只能以静态的方式调用内部方法
 * 
 * @author QinerG(QinerG@gmail.com)
 */
public abstract class ValidationUtils {

	private static final String mobile_regex = "1\\d{10}";
	private static final String email_regex = "^([\\w-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([\\w-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\\]?)$";
	private static final String chinese_regex = "[\u4e00-\u9fa5]+";
	private static final String qq_regex = "[1-9][0-9]{4,}";
	private static final String post_regex = "[1-9]\\d{5}(?!\\d)";
	private static final String account_regex = "^[a-zA-Z][a-zA-Z0-9_]+";

	/**
	 * 必填字段验证
	 * 
	 * @param fieldName
	 *            待验证字段名
	 * @param obj
	 *            待验证对象
	 * @param errorMsg
	 *            验证错误后的提示语
	 * @param errors
	 *            存储错误信息的对象
	 * @return 返回是否通过验证
	 */
	public static boolean required(String fieldName, Object target, String errorMsg, Errors errors) {
		if (null == target) {
			errors.add(fieldName, errorMsg);
			return false;
		}
		if (target instanceof String && Strings.isBlank((String) target)) {
			errors.add(fieldName, errorMsg);
			return false;
		}
		return true;
	}

	/**
	 * 正则表达式验证
	 * 
	 * @param fieldName
	 * @param obj
	 * @param regex
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean regex(String fieldName, Object target, String regex, String errorMsg, Errors errors) {
		if (null == target || !(target instanceof String)) {
			errors.add(fieldName, errorMsg);
			return false;
		}

		final Matcher m = Pattern.compile(regex, Pattern.MULTILINE + Pattern.DOTALL).matcher((String) target);
		if (!m.matches()) {
			errors.add(fieldName, errorMsg);
			return false;
		}
		return true;
	}

	/**
	 * 手机号验证
	 * 
	 * @param fieldName
	 * @param obj
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean mobile(String fieldName, Object target, String errorMsg, Errors errors) {
		return regex(fieldName, target, mobile_regex, errorMsg, errors);
	}

	/**
	 * 国内邮政编码验证
	 * 
	 * @param fieldName
	 * @param target
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean post(String fieldName, Object target, String errorMsg, Errors errors) {
		return regex(fieldName, target, post_regex, errorMsg, errors);
	}

	/**
	 * Email 验证
	 * 
	 * @param fieldName
	 * @param obj
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean email(String fieldName, Object target, String errorMsg, Errors errors) {
		return regex(fieldName, target, email_regex, errorMsg, errors);
	}

	/**
	 * 只允许中文验证
	 * 
	 * @param fieldName
	 * @param obj
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean chinese(String fieldName, Object target, String errorMsg, Errors errors) {
		return regex(fieldName, target, chinese_regex, errorMsg, errors);
	}

	/**
	 * QQ号 验证
	 * 
	 * @param fieldName
	 * @param obj
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean qq(String fieldName, Object target, String errorMsg, Errors errors) {
		return regex(fieldName, target, qq_regex, errorMsg, errors);
	}

	/**
	 * 账号验证
	 * 
	 * @param fieldName
	 * @param target
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean account(String fieldName, Object target, String errorMsg, Errors errors) {
		return regex(fieldName, target, account_regex, errorMsg, errors);
	}

	/**
	 * 重复性验证。两个字段的值必须一致，允许空值。
	 * 
	 * @param fieldName
	 * @param target
	 * @param repeatValue
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean repeat(	String fieldName,
									Object target,
									Object repeatValue,
									String errorMsg,
									Errors errors) {
		if (!(null == target) && !target.equals(repeatValue)) {
			errors.add(fieldName, errorMsg);
			return false;
		}
		if (!(null == repeatValue) && !repeatValue.equals(target)) {
			errors.add(fieldName, errorMsg);
			return false;
		}
		return true;
	}

	/**
	 * 字符串长度必须在一定区间范围内验证
	 * 
	 * @param fieldName
	 * @param obj
	 * @param interval
	 *            长度限定区间，如果传空的数组也不会报错，但验证将总是会通过
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean stringLength(	String fieldName,
										Object target,
										int[] interval,
										String errorMsg,
										Errors errors) {
		int minLength = 0;
		int maxLength = Integer.MAX_VALUE;
		if (interval.length >= 1) {
			minLength = interval[0];
		}
		if (interval.length >= 2) {
			maxLength = interval[1];
		}
		return stringLength(fieldName, target, minLength, maxLength, errorMsg, errors);
	}

	/**
	 * 字符串长度必须在一定区间范围内验证
	 * 
	 * @param fieldName
	 * @param obj
	 * @param minLength
	 * @param maxLength
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean stringLength(	String fieldName,
										Object target,
										int minLength,
										int maxLength,
										String errorMsg,
										Errors errors) {
		if (null == target || !(target instanceof String)) {
			errors.add(fieldName, errorMsg);
			return false;
		}
		String str = (String) target;
		if (str.length() < minLength || str.length() > maxLength) {
			errors.add(fieldName, errorMsg);
			return false;
		}
		return true;
	}

	/**
	 * 判断指定值是否在某个区间
	 * 
	 * @param fieldName
	 * @param target
	 * @param interval
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean limit(String fieldName, Object target,	double[] interval,	String errorMsg, Errors errors) {
		double minLength = 0;
		double maxLength = Double.MAX_VALUE;
		if (interval.length >= 1) {
			minLength = interval[0];
		}
		if (interval.length >= 2) {
			maxLength = interval[1];
		}
		return limit(fieldName, target, minLength, maxLength, errorMsg, errors);
	}

	/**
	 * 判断指定值是否在某个区间,兼容 int、long、float、double
	 * 
	 * @param fieldName
	 * @param target
	 * @param interval
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean limit(String fieldName, Object target,	double minValue, double maxValue, String errorMsg, Errors errors) {
		if (null == target) {
			errors.add(fieldName, errorMsg);
			return false;
		}
		Double d = null;
		if (target instanceof Double) {
			d = (Double) target;
		} else if (target instanceof Integer) {
			d = ((Integer) target).doubleValue();
		} else if (target instanceof Long) {
			d = ((Long) target).doubleValue();
		} else if (target instanceof Float) {
			d = ((Float) target).doubleValue();
		}
		if (d == null || d < minValue || d > maxValue) {
			errors.add(fieldName, errorMsg);
			return false;
		}
		return true;
	}

	/**
	 * 通过 nutz 自带的 el 表达式进行验证
	 * @param fieldName
	 * @param obj
	 * @param el 表达式
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean el(String fieldName, Object obj, String el, String errorMsg, Errors errors) {
		Context context = Lang.context();
		context.set("value", obj);
		Object val = El.eval(context, el);
		if (val == null)
		    return true;
		if (val instanceof Boolean) {
			if ((Boolean) val) {
				return (Boolean) val;
			} else {
				errors.add(fieldName, errorMsg);
				return false;
			}
		}
		errors.add(fieldName, errorMsg);
        return false;
	}

	protected static Map<String, FastMethod> customMethods = new HashMap<String, FastMethod>();
	/**
	 * 自定义验证方法
	 * 
	 * @param fieldName
	 * @param obj
	 * @param customFunction
	 *            自定义验证方法名称，注意该方法必须在 obj 里用 public 声明，且返回值为 boolean 型，否则会抛出异常
	 * @param errorMsg
	 * @param errors
	 * @return
	 */
	public static boolean custom(	String fieldName, Object obj, String customFunction, String errorMsg, Errors errors) {
		Mirror<?> mirror = Mirror.me(obj.getClass());
		Method[] mds = mirror.getMethods();
		boolean find = false;
		// 如果只写了个一个下划线,那就是自动找方法模式
        if ("_".equals(customFunction))
            customFunction = "verify" + Strings.upperFirst(fieldName);
        FastMethod fm = customMethods.get(obj.getClass().getName() + "#" + fieldName);
        if (fm != null) {
            try {
                Boolean ret = (Boolean) fm.invoke(obj, fieldName, errorMsg, errors);
                if (ret != null && !ret) {
                    errors.add(fieldName, errorMsg);
                    return false;
                }
                return true;
            }
            catch (Exception e) {
            }
        }
		for (Method md : mds) {
			if (md.getName().equals(customFunction)) {
			    int paramCount = md.getParameterTypes().length;
                try {
                    Boolean ret = null;
                    // 兼容老写法, 只传一个参数,请用新方法
                    if (paramCount == 0) {
						find = true;
						ret = (Boolean) md.invoke(obj);
                    }
                    // 新写法更强大, 传3个参数, 用户代码通过Errors自行添加错误信息
                    // public boolean checkUserAge(String fieldName, String errorMsg, Errors errors)
                    else if (paramCount == 3) {
						find = true;
                        fm = FastClassFactory.get(md);
                        customMethods.put(obj.getClass().getName() + "#" + fieldName, fm);
                        ret = (Boolean)fm.invoke(obj, fieldName, errorMsg, errors);
                    }
                    else {
                        continue;
                    }
                    if (ret != null && !ret) {
                        errors.add(fieldName, errorMsg);
                        return false;
                    }
                    return true;
                }
                catch (Throwable e) {
                    errors.add(fieldName, errorMsg);
                    return false;
                }
			}
		}
		if (!find) {
			// 没有找到指定的方法，为用户指出明确错误信息
			errors.add(fieldName, "没有找到指定的效验方法");
			return false;
		}
		return true;
	}

	/**
	 * 检查方法的参数中是否存在 Errors 的对象，没有则返回空
	 * 
	 * @param argsClass
	 * @param args
	 * @return
	 */
	public static Errors checkArgs(Class<?>[] argsClass, Object... args) {
		Errors es = null;
		for (int i = 0; i < argsClass.length; i++) {
			if (argsClass[i] == Errors.class) {
				if (args[i] == null) {
					args[i] = es = new Errors();
				} else {
					es = (Errors) args[i];
				}
				break;
			}
		}

		return es;
	}

}
