package org.seasar.dao.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import javax.sql.DataSource;

import org.seasar.dao.BeanMetaData;
import org.seasar.extension.jdbc.PropertyType;
import org.seasar.extension.jdbc.UpdateHandler;
import org.seasar.extension.jdbc.impl.BasicHandler;
import org.seasar.framework.beans.PropertyDesc;
import org.seasar.framework.exception.SQLRuntimeException;
import org.seasar.framework.log.Logger;
import org.seasar.framework.util.ConnectionUtil;
import org.seasar.framework.util.IntegerConversionUtil;
import org.seasar.framework.util.PreparedStatementUtil;
import org.seasar.framework.util.StatementUtil;

/**
 * @author higa
 *  
 */
public abstract class AbstractAutoHandler extends BasicHandler implements
		UpdateHandler {

	private static Logger logger_ = Logger.getLogger(AbstractAutoHandler.class);

	private BeanMetaData beanMetaData_;
	
	private Object[] bindVariables_;
	
	private Timestamp timestamp_;
	
	private Integer versionNo_;
	
	private PropertyType[] propertyTypes_;

	public AbstractAutoHandler(DataSource dataSource, BeanMetaData beanMetaData, PropertyType[] propertyTypes) {
		setDataSource(dataSource);
		beanMetaData_ = beanMetaData;
		propertyTypes_ = propertyTypes;
	}

	public BeanMetaData getBeanMetaData() {
		return beanMetaData_;
	}
	
	protected static Logger getLogger() {
		return logger_;
	}
	
	protected Object[] getBindVariables() {
		return bindVariables_;
	}
	
	protected void setBindVariables(Object[] bindVariables) {
		bindVariables_ = bindVariables;
	}
	
	protected Timestamp getTimestamp() {
		return timestamp_;
	}
	
	protected void setTimestamp(Timestamp timestamp) {
		timestamp_ = timestamp;
	}
	
	protected Integer getVersionNo() {
		return versionNo_;
	}
	
	protected void setVersionNo(Integer versionNo) {
		versionNo_ = versionNo;
	}
	
	protected PropertyType[] getPropertyTypes() {
		return propertyTypes_;
	}
	
	protected void setPropertyTypes(PropertyType[] propertyTypes) {
		propertyTypes_ = propertyTypes;
	}

	public int execute(Object[] args) throws SQLRuntimeException {
		Connection connection = getConnection();
		try {
			return execute(connection, args[0]);
		} finally {
			ConnectionUtil.close(connection);
		}
	}

	protected int execute(Connection connection, Object bean) {
		preUpdateBean(bean);
		setupBindVariables(bean);
		if (logger_.isDebugEnabled()) {
			logger_.debug(getCompleteSql(bindVariables_));
		}
		PreparedStatement ps = prepareStatement(connection);
		int ret = -1;
		try {
			bindArgs(ps, bindVariables_);
			ret = PreparedStatementUtil.executeUpdate(ps);
		} finally {
			StatementUtil.close(ps);
		}
		postUpdateBean(bean);
		return ret;
	}

	protected void preUpdateBean(Object bean) {	
	}
	
	protected void postUpdateBean(Object bean) {	
	}

	protected abstract void setupBindVariables(Object bean);
	
	protected void setupInsertBindVariables(Object bean) {
		List bindVariables = new ArrayList();
		for (int i = 0; i < propertyTypes_.length; ++i) {
			PropertyType pt = propertyTypes_[i];
			if (pt.getPropertyName().equalsIgnoreCase(BeanMetaData.TIMESTAMP_PROPERTY_NAME)) {
				setTimestamp(new Timestamp(new Date().getTime()));
				bindVariables.add(getTimestamp());
			} else if (pt.getPropertyName().equals(BeanMetaData.VERSION_NO_PROPERTY_NAME)) {
				setVersionNo(new Integer(0));
				bindVariables.add(getVersionNo());
			} else {
				bindVariables.add(pt.getPropertyDesc().getValue(bean));
			}
		}
		setBindVariables(bindVariables.toArray());
	}
	
	protected void setupUpdateBindVariables(Object bean) {
		List bindVariables = new ArrayList();
		for (int i = 0; i < propertyTypes_.length; ++i) {
			PropertyType pt = propertyTypes_[i];
			if (pt.getPropertyName().equalsIgnoreCase(BeanMetaData.TIMESTAMP_PROPERTY_NAME)) {
				setTimestamp(new Timestamp(new Date().getTime()));
				bindVariables.add(getTimestamp());
			} else if (pt.getPropertyName().equals(BeanMetaData.VERSION_NO_PROPERTY_NAME)) {
				Object value = pt.getPropertyDesc().getValue(bean);
				int intValue = IntegerConversionUtil.toPrimitiveInt(value) + 1;
				setVersionNo(new Integer(intValue));
				bindVariables.add(getVersionNo());
			} else {
				bindVariables.add(pt.getPropertyDesc().getValue(bean));
			}
		}
		addAutoUpdateWhereBindVariables(bindVariables, bean);
		setBindVariables(bindVariables.toArray());
	}
	
	protected void setupDeleteBindVariables(Object bean) {
		List bindVariables = new ArrayList();
		addAutoUpdateWhereBindVariables(bindVariables, bean);
		setBindVariables(bindVariables.toArray());
	}
	
	protected void addAutoUpdateWhereBindVariables(List bindVariables, Object bean) {
		BeanMetaData bmd = getBeanMetaData();
		for (int i = 0; i < bmd.getPrimaryKeySize(); ++i) {
			PropertyType pt = bmd.getPropertyTypeByColumnName(bmd.getPrimaryKey(i));
			bindVariables.add(pt.getPropertyDesc().getValue(bean));
		}
		if (bmd.hasVersionNoPropertyType()) {
			PropertyType pt = bmd.getVersionNoPropertyType();
			bindVariables.add(pt.getPropertyDesc().getValue(bean));
		}
		if (bmd.hasTimestampPropertyType()) {
			PropertyType pt = bmd.getTimestampPropertyType();
			bindVariables.add(pt.getPropertyDesc().getValue(bean));
		}
	}
	
	protected void updateTimestampIfNeed(Object bean) {
		if (getTimestamp() != null) {
			PropertyDesc pd = getBeanMetaData().getTimestampPropertyType().getPropertyDesc();
			pd.setValue(bean, getTimestamp());
		}
	}
	
	protected void updateVersionNoIfNeed(Object bean) {
		if (getVersionNo() != null) {
			PropertyDesc pd = getBeanMetaData().getVersionNoPropertyType().getPropertyDesc();
			pd.setValue(bean, getVersionNo());
		}
	}
}