package org.seasar.hibernate.dao.impl;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

import java.util.HashMap;
import java.util.Map;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.cfg.Configuration;

import org.seasar.framework.beans.BeanDesc;
import org.seasar.framework.beans.MethodNotFoundRuntimeException;
import org.seasar.framework.beans.factory.BeanDescFactory;
import org.seasar.framework.util.FieldUtil;
import org.seasar.framework.util.MethodUtil;
import org.seasar.framework.util.ResourceUtil;
import org.seasar.framework.util.StringUtil;
import org.seasar.hibernate.HibernateRuntimeException;
import org.seasar.hibernate.S2SessionFactory;
import org.seasar.hibernate.dao.HibernateCommand;
import org.seasar.hibernate.dao.HibernateDaoMetaData;
import org.seasar.hibernate.impl.S2SessionFactoryImpl;

/**
 * @author kenichi_okazaki
 */
public class HibernateDaoMetaDataImpl implements HibernateDaoMetaData {

	private static final String[] INSERT_NAMES = new String[] { "insert",
			"create", "add", "save" };

	private static final String[] UPDATE_NAMES = new String[] { "update",
			"modify", "store" };

	private static final String[] DELETE_NAMES = new String[] { "delete",
			"remove" };

	private static final String[] SAVE_OR_UPDATE_NAMES = new String[] { "saveOrUpdate" };

//	private static final String[] LOAD_NAMES = new String[] { "load" };

	private Class daoClass_;

	private BeanDesc daoBeanDesc_;

	private Class beanClass_;

	private Map hibernateCommands_ = new HashMap();

	private S2SessionFactory s2sessionFactory_;

	private Map namedQueries_;

	/**
	 * @param daoClass
	 */
	public HibernateDaoMetaDataImpl(S2SessionFactory s2sessionFactory,
			Class daoClass) {

		s2sessionFactory_ = s2sessionFactory;

		daoClass_ = daoClass;
		daoBeanDesc_ = BeanDescFactory.getBeanDesc(daoClass);
		Field beanField = daoBeanDesc_.getField(BEAN_KEY);
		beanClass_ = (Class) FieldUtil.get(beanField, null);

		seturHibernateNamedQuerys();

		setupHibernateCommand();

	}

	private void seturHibernateNamedQuerys() {
		Configuration cfg = new Configuration();
		String hibernateConfigPath = ((S2SessionFactoryImpl) s2sessionFactory_)
				.getConfigPath();
		try {
			cfg.configure(ResourceUtil.getResource(hibernateConfigPath));
		} catch (HibernateException ex) {
			throw new HibernateRuntimeException(ex);
		}
		namedQueries_ = cfg.getNamedQueries();
	}

	private void setupHibernateCommand() {
		String[] names = daoBeanDesc_.getMethodNames();
		for (int i = 0; i < names.length; ++i) {
			Method[] methods = daoBeanDesc_.getMethods(names[i]);
			if (methods.length == 1 && MethodUtil.isAbstract(methods[0])) {
				setupMethod(methods[0]);
			}
		}
	}

	private void setupMethod(Method method) {
		String methodName = method.getName();

		if (isMethod(INSERT_NAMES, methodName)) {
			setupInsertMethodByAuto(method);

		} else if (isMethod(UPDATE_NAMES, methodName)) {
			setupUpdateMethodByAuto(method);

		} else if (isMethod(DELETE_NAMES, methodName)) {
			setupDeleteMethodByAuto(method);

		} else if (isMethod(SAVE_OR_UPDATE_NAMES, methodName)) {
			setupSaveOrUpdateMethodByAuto(method);

//		} else if (isMethod(LOAD_NAMES, methodName)) {
//			setupLoadMethodByAuto(method);

		} else {

			setupSelectMethod(method);
		}
	}

	private boolean isMethod(String[] methodNameList, String methodName) {
		for (int i = 0; i < methodNameList.length; ++i) {
			if (methodName.startsWith(methodNameList[i])) {
				return true;
			}
		}
		return false;
	}

	
	private void setupSelectMethod(Method method) {
		String namedQueryLongPath = daoClass_.getName() +"_"+method.getName();
		String namedQueryShortPath = method.getName();
		String hqlQuery = getQuery(method.getName());

		if(hqlQuery != null){
			setupSelectMethodByHql( method);
		}else if (isNamedQuery(namedQueryLongPath)) {
			setupSelectMethodByNamedQuery(method, namedQueryLongPath);
		}else if (isNamedQuery(namedQueryShortPath)) {
			setupSelectMethodByNamedQuery(method, namedQueryShortPath);
		}else{
			setupSelectMethodByAuto(method);
		}
		
	}
	
	private boolean isNamedQuery(String namedQueryName) {
		return namedQueries_.containsKey(namedQueryName);
	}

	private String[] getArgNames(String methodName) {
		String argsKey = methodName + ARGS_KEY_SUFFIX;
		if (daoBeanDesc_.hasField(argsKey)) {
			Field argNamesField = daoBeanDesc_.getField(argsKey);
			String argNames = (String) FieldUtil.get(argNamesField, null);
			return StringUtil.split(argNames, " ,");
		} else {
			return new String[0];
		}
	}

	private String getQuery(String methodName) {
		String queryKey = methodName + HQL_KEY_SUFFIX;
		if (daoBeanDesc_.hasField(queryKey)) {
			Field queryNamesField = daoBeanDesc_.getField(queryKey);
			return (String) FieldUtil.get(queryNamesField, null);
		} else {
			return null;
		}
	}

	private void setupInsertMethodByAuto(Method method) {
		InsertCommand cmd = new InsertCommand(s2sessionFactory_);
		hibernateCommands_.put(method.getName(), cmd);
	}

	private void setupSaveOrUpdateMethodByAuto(Method method) {
		SaveOrUpdateCommand cmd = new SaveOrUpdateCommand(s2sessionFactory_);
		hibernateCommands_.put(method.getName(), cmd);
	}

	private void setupUpdateMethodByAuto(Method method) {
		UpdateCommand cmd = new UpdateCommand(s2sessionFactory_);
		hibernateCommands_.put(method.getName(), cmd);
	}

	private void setupDeleteMethodByAuto(Method method) {
		DeleteCommand cmd = new DeleteCommand(s2sessionFactory_);
		hibernateCommands_.put(method.getName(), cmd);
	}



	private void setupSelectMethodByHql(Method method){

		HqlQueryCommand cmd = new HqlQueryCommand(s2sessionFactory_,beanClass_, method );
		cmd.setArgNames(getArgNames(method.getName()));
		cmd.setHqlQuery( getQuery(method.getName()));

		hibernateCommands_.put(method.getName(), cmd);
}

	private void setupSelectMethodByAuto(Method method){

			AutoQueryCommand cmd = new AutoQueryCommand(s2sessionFactory_,beanClass_, method);
			cmd.setArgNames(getArgNames(method.getName()));
			hibernateCommands_.put(method.getName(), cmd);
	}
		
	private void setupSelectMethodByNamedQuery(Method method, String namedQueryPath) {

		NamedQueryCommand cmd = new NamedQueryCommand(s2sessionFactory_,beanClass_, method);
		cmd.setArgNames(getArgNames(method.getName()));
		cmd.setNamedQueryPath_( namedQueryPath);
		hibernateCommands_.put(method.getName(), cmd);
		
	}

	/*
	 * (”ñ Javadoc)
	 * 
	 * @see org.srasar.hibernate.dao.HibernateDaoMetaData#getBeanClass()
	 */
	public Class getBeanClass() {
		return beanClass_;
	}

	/*
	 * (”ñ Javadoc)
	 * 
	 * @see org.srasar.hibernate.dao.HibernateDaoMetaData#hasHibernateCommand(java.lang.String)
	 */
	public boolean hasHibernateCommand(String methodName) {
		if ((HibernateCommand) hibernateCommands_.get(methodName) == null) {
			return false;
		} else {
			return true;
		}
	}

	/*
	 * (”ñ Javadoc)
	 * 
	 * @see org.srasar.hibernate.dao.HibernateDaoMetaData#getHibernateCommand(java.lang.String)
	 */
	public HibernateCommand getHibernateCommand(String methodName)
			throws MethodNotFoundRuntimeException {
		return (HibernateCommand) hibernateCommands_.get(methodName);
	}

}