/*
 * This file is part of Nuts Framework.
 * Copyright (C) 2009 Nuts Develop Team.
 *
 * Nuts Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License any later version.
 * 
 * Nuts Framework is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nuts Framework. If not, see <http://www.gnu.org/licenses/>.
 */
package nuts.exts.ibatis;

import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.Map;

import nuts.core.orm.dao.DataAccessClient;
import nuts.core.orm.dao.DataAccessException;
import nuts.core.orm.dao.DataHandler;
import nuts.core.orm.dao.ModelDAO;
import nuts.core.orm.dao.ModelMetaData;
import nuts.core.orm.sql.SqlDataAccessSession;
import nuts.core.sql.SqlUtils;

import com.ibatis.sqlmap.client.SqlMapSession;

/**
 */
public class IBatisDataAccessSession implements SqlDataAccessSession {
	private IBatisDataAccessClient dataAccessClient;
	private SqlMapSession sqlMapSession;
	private Connection connection;
	private boolean autoCommit = false;
	private boolean dirty = false;
	
	/**
	 * @param dataAccessClient dataAccessClient
	 * @param autoCommit autoCommit
	 */
	public IBatisDataAccessSession(IBatisDataAccessClient dataAccessClient, boolean autoCommit) {
		super();
		this.dataAccessClient = dataAccessClient;
		this.autoCommit = autoCommit;
	}

	/**
	 * @return Data Access Client
	 */
	public DataAccessClient getDataAccessClient() {
		return dataAccessClient;
	}

	/**
	 * @return the sqlMapSession
	 * @throws SQLException 
	 */
	private SqlMapSession getSqlMapSession() throws SQLException {
		if (sqlMapSession == null) {
			connection = dataAccessClient.getSqlMapClient().getDataSource().getConnection();
			connection.setAutoCommit(autoCommit);
			sqlMapSession = dataAccessClient.getSqlMapClient().openSession(connection);
		}
		return sqlMapSession;
	}

	/**
	 * @return model meta data map
	 */
	public Map<String, ModelMetaData> getMetaDataMap() {
		return dataAccessClient.getMetaDataMap();
	}
	
	/**
	 * @param name model name
	 * @return model meta data
	 */
	public ModelMetaData getMetaData(String name) {
		return dataAccessClient.getMetaData(name);
	}

	/**
	 * @param name model name
	 * @return modelDao
	 */
	public ModelDAO getModelDAO(String name) {
		return dataAccessClient.getModelDAO(name, this);
	}

	/**
	 * close session
	 */
	public void close() {
		try {
			commit();
		}
		catch (DataAccessException e) {
		}

		try {
			if (sqlMapSession != null) {
				sqlMapSession.close();
				sqlMapSession = null;
			}
		}
		finally {
			SqlUtils.closeQuietly(connection);
			connection = null;
		}
	}
	
	/**
	 * @return true if session is closed
	 */
	public boolean isClosed() {
		return connection == null;
	}

	/**
	 * commit
	 * @throws DataAccessException if a data access error occurs
	 */
	public void commit() throws DataAccessException {
		try {
			if (dirty) {
				dirty = false;
				if (connection != null) {
					connection.commit();
				}
			}
		} 
		catch (SQLException e) {
			throw new DataAccessException(e);
		}
	}

	/**
	 * rollback
	 * @throws DataAccessException if a data access error occurs
	 */
	public void rollback() throws DataAccessException {
		try {
			if (dirty) {
				dirty = false;
				if (connection != null) {
					connection.rollback();
				}
			}
		} 
		catch (SQLException e) {
			throw new DataAccessException(e);
		}
	}
	
	/**
	 * Insert is a bit different from other update methods, as it provides
	 * facilities for returning the primary key of the newly inserted row
	 * (rather than the effected rows). This functionality is of course
	 * optional.
	 * <p/>
	 * The parameter object is generally used to supply the input data for the
	 * INSERT values.
	 * 
	 * @param statement The name of the statement to execute.
	 * @param parameter The parameter object (e.g. JavaBean, Map, XML
	 *            etc.).
	 * @return The primary key of the newly inserted row. This might be
	 *         automatically generated by the RDBMS, or selected from a sequence
	 *         table or other source.
	 * @throws java.sql.DataAccessException If an error occurs.
	 */
	public Object insert(String statement, Object parameter) throws DataAccessException {
		try {
			dirty = true;
			return getSqlMapSession().insert(statement, parameter);
		} 
		catch (SQLException e) {
			throw new DataAccessException(e);
		}
	}

	/**
	 * Update can also be used for any other update statement type, such as
	 * inserts and deletes. Update returns the number of rows effected.
	 * <p/>
	 * The parameter object is generally used to supply the input data for the
	 * UPDATE values as well as the WHERE clause parameter(s).
	 * 
	 * @param statement The name of the statement to execute.
	 * @param parameter The parameter object (e.g. JavaBean, Map, XML
	 *            etc.).
	 * @return The number of rows effected.
	 * @throws java.sql.DataAccessException If an error occurs.
	 */
	public int update(String statement, Object parameter) throws DataAccessException {
		try {
			dirty = true;
			return getSqlMapSession().update(statement, parameter);
		} 
		catch (SQLException e) {
			throw new DataAccessException(e);
		}
	}

	/**
	 * Delete returns the number of rows effected.
	 * <p/>
	 * The parameter object is generally used to supply the input data for the
	 * WHERE clause parameter(s) of the DELETE statement.
	 * 
	 * @param statement The name of the statement to execute.
	 * @param parameter The parameter object (e.g. JavaBean, Map, XML
	 *            etc.).
	 * @return The number of rows effected.
	 * @throws java.sql.DataAccessException If an error occurs.
	 */
	public int delete(String statement, Object parameter) throws DataAccessException {
		try {
			dirty = true;
			return getSqlMapSession().delete(statement, parameter);
		} 
		catch (SQLException e) {
			throw new DataAccessException(e);
		}
	}

	/**
	 * The parameter object is generally used to supply the input data for the
	 * WHERE clause parameter(s) of the SELECT statement.
	 * 
	 * @param statement The name of the statement to execute.
	 * @param parameter The parameter object (e.g. JavaBean, Map, XML
	 *            etc.).
	 * @return The single result object populated with the result set data, or
	 *         null if no result was found
	 * @throws java.sql.DataAccessException If more than one result was found, or if
	 *             any other error occurs.
	 */
	public Object selectOne(String statement, Object parameter)
			throws DataAccessException {
		try {
			return getSqlMapSession().queryForObject(statement, parameter);
		} 
		catch (SQLException e) {
			throw new DataAccessException(e);
		}
	}

	/**
	 * The parameter object is generally used to supply the input data for the
	 * WHERE clause parameter(s) of the SELECT statement.
	 * 
	 * @param statement The name of the statement to execute.
	 * @param parameter The parameter object (e.g. JavaBean, Map, XML
	 *            etc.).
	 * @return A List of result objects.
	 * @throws java.sql.DataAccessException If an error occurs.
	 */
	public List selectList(String statement, Object parameter) throws DataAccessException {
		try {
			return getSqlMapSession().queryForList(statement, parameter);
		} 
		catch (SQLException e) {
			throw new DataAccessException(e);
		}
	}

	/**
	 * Executes a mapped SQL SELECT statement that returns data to populate a
	 * number of result objects within a certain range.
	 * <p/>
	 * The parameter object is generally used to supply the input data for the
	 * WHERE clause parameter(s) of the SELECT statement.
	 * 
	 * @param statement The name of the statement to execute.
	 * @param parameter The parameter object (e.g. JavaBean, Map, XML
	 *            etc.).
	 * @param offset The number of results to ignore.
	 * @param limit The maximum number of results to return.
	 * @return A List of result objects.
	 * @throws java.sql.DataAccessException If an error occurs.
	 */
	public List selectList(String statement, Object parameter, int offset, int limit)
			throws DataAccessException {
		try {
			return getSqlMapSession().queryForList(statement, parameter, offset, limit);
		} 
		catch (SQLException e) {
			throw new DataAccessException(e);
		}
	}

	/**
	 * This is generally a good approach to take when dealing with large sets of
	 * records (i.e. hundreds, thousands...) that need to be processed without
	 * eating up all of the system resources.
	 * <p/>
	 * The parameter object is generally used to supply the input data for the
	 * WHERE clause parameter(s) of the SELECT statement.
	 * 
	 * @param statement The name of the statement to execute.
	 * @param parameter The parameter object (e.g. JavaBean, Map, XML
	 *            etc.).
	 * @param dataHandler A DataHandler instance
	 * @return data count
	 * @throws java.sql.DataAccessException If an error occurs.
	 */
	public int selectWithDataHandler(String statement, Object parameter,
			DataHandler dataHandler) throws DataAccessException {
		try {
			IBatisRowHandler rh = new IBatisRowHandler(dataHandler);
			getSqlMapSession().queryWithRowHandler(statement, parameter, rh);
			return rh.getCount();
		} 
		catch (SQLException e) {
			throw new DataAccessException(e);
		}
	}

	/**
	 * This is generally a good approach to take when dealing with large sets of
	 * records (i.e. hundreds, thousands...) that need to be processed without
	 * eating up all of the system resources.
	 * <p/>
	 * The parameter object is generally used to supply the input data for the
	 * WHERE clause parameter(s) of the SELECT statement.
	 * 
	 * @param statement The name of the statement to execute.
	 * @param parameter The parameter object (e.g. JavaBean, Map, XML
	 *            etc.).
	 * @param offset The number of results to ignore.
	 * @param limit The maximum number of results to return.
	 * @param dataHandler A DataHandler instance
	 * @return data count
	 * @throws java.sql.DataAccessException If an error occurs.
	 */
	public int selectWithDataHandler(String statement, Object parameter, 
			int offset, int limit,
			DataHandler dataHandler) throws DataAccessException {
		try {
			IBatisRowHandler rh = new IBatisRowHandler(dataHandler, offset, limit);
			getSqlMapSession().queryWithRowHandler(statement, parameter, rh);
			return rh.getCount();
		} 
		catch (SQLException e) {
			throw new DataAccessException(e);
		}
	}
}
