/*
 * This file is part of Nuts Framework.
 * Copyright(C) 2009-2012 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.mybatis;

import nuts.core.dao.DaoException;
import nuts.core.dao.DaoClient;
import nuts.core.dao.DataHandler;
import nuts.core.dao.ModelDAO;
import nuts.core.dao.ModelMetaData;
import nuts.core.dao.sql.SqlDataAccessSession;
import nuts.core.lang.time.StopWatch;
import nuts.core.log.Log;
import nuts.core.log.Logs;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;

/**
 */
public class MyBatisDataAccessSession implements SqlDataAccessSession {
	private static Log log = Logs.getLog(MyBatisDataAccessSession.class);
	
	private MyBatisDataAccessClient dataAccessClient;
	private boolean autoCommit;
	private SqlSession sqlSession;
	
	
	/**
	 */
	public MyBatisDataAccessSession() {
		super();
	}

	/**
	 * @param dataAccessClient client
	 * @param autoCommit auto commit
	 */
	public MyBatisDataAccessSession(MyBatisDataAccessClient dataAccessClient, boolean autoCommit) {
		super();
		this.dataAccessClient = dataAccessClient;
		this.autoCommit = autoCommit;
	}

	/**
	 * @return Data Access Client
	 */
	public DaoClient getDaoClient() {
		return dataAccessClient;
	}
	
	/**
	 * @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);
	}

	/**
	 * @return the sqlSession
	 */
	private SqlSession getSqlSession() {
		if (sqlSession == null) {
			sqlSession = dataAccessClient.getSqlSessionFactory().openSession(autoCommit);
		}
		
		return sqlSession;
	}

	/**
	 * close session
	 */
	public void close() {
		if (sqlSession != null) {
			sqlSession.close();
			sqlSession = null;
		}
	}
	
	/**
	 * @return true if session is closed
	 */
	public boolean isClosed() {
		return sqlSession == null;
	}

	/**
	 * commit
	 * @throws DaoException if a data access error occurs
	 */
	public void commit() throws DaoException {
		try {
			if (sqlSession != null) { 
				sqlSession.commit();
			}
		} 
		catch (RuntimeException e) {
			throw new DaoException(e);
		}
	}

	/**
	 * rollback
	 * @throws DaoException if a data access error occurs
	 */
	public void rollback() throws DaoException {
		try {
			if (sqlSession != null) {
				sqlSession.rollback();
			}
		} 
		catch (RuntimeException e) {
			throw new DaoException(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 DaoException If an error occurs.
	 */
	public Object insert(String statement, Object parameter) throws DaoException {
		StopWatch watch = new StopWatch(true);
		try {
			return getSqlSession().insert(statement, parameter);
		} 
		catch (RuntimeException e) {
			throw new DaoException(e);
		}
		finally {
			if (log.isDebugEnabled()) {
				log.debug("insert [" + statement + "] took " + watch);
			}
		}
	}

	/**
	 * 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 DaoException If an error occurs.
	 */
	public int update(String statement, Object parameter) throws DaoException {
		StopWatch watch = new StopWatch(true);
		try {
			return getSqlSession().update(statement, parameter);
		} 
		catch (RuntimeException e) {
			throw new DaoException(e);
		}
		finally {
			if (log.isDebugEnabled()) {
				log.debug("update [" + statement + "] took " + watch);
			}
		}
	}

	/**
	 * 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 DaoException If an error occurs.
	 */
	public int delete(String statement, Object parameter) throws DaoException {
		StopWatch watch = new StopWatch(true);
		try {
			return getSqlSession().delete(statement, parameter);
		} 
		catch (RuntimeException e) {
			throw new DaoException(e);
		}
		finally {
			if (log.isDebugEnabled()) {
				log.debug("delete [" + statement + "] took " + watch);
			}
		}
	}

	/**
	 * 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 DaoException If more than one result was found, or if
	 *             any other error occurs.
	 */
	public Object selectOne(String statement, Object parameter)
			throws DaoException {
		StopWatch watch = new StopWatch(true);
		try {
			return getSqlSession().selectOne(statement, parameter);
		} 
		catch (RuntimeException e) {
			throw new DaoException(e);
		}
		finally {
			if (log.isDebugEnabled()) {
				log.debug("selectOne [" + statement + "] took " + watch);
			}
		}
	}

	/**
	 * 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 DaoException If an error occurs.
	 */
	public List selectList(String statement, Object parameter) throws DaoException {
		StopWatch watch = new StopWatch(true);
		try {
			return getSqlSession().selectList(statement, parameter);
		} 
		catch (RuntimeException e) {
			throw new DaoException(e);
		}
		finally {
			if (log.isDebugEnabled()) {
				log.debug("selectList [" + statement + "] took " + watch);
			}
		}
	}

	/**
	 * 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 DaoException If an error occurs.
	 */
	public List selectList(String statement, Object parameter, int offset, int limit)
			throws DaoException {
		StopWatch watch = new StopWatch(true);
		try {
			return getSqlSession().selectList(statement, parameter, new RowBounds(offset, limit));
		} 
		catch (RuntimeException e) {
			throw new DaoException(e);
		}
		finally {
			if (log.isDebugEnabled()) {
				log.debug("selectList [" + statement + " " + offset + "," + limit + "] took " + watch);
			}
		}
	}

	/**
	 * 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 DaoException If an error occurs.
	 */
	public int selectWithDataHandler(String statement, Object parameter,
			DataHandler dataHandler) throws DaoException {
		StopWatch watch = new StopWatch(true);
		try {
			MyBatisResultHandler rh = new MyBatisResultHandler(dataHandler);
			getSqlSession().select(statement, parameter, rh);
			return rh.getCount();
		} 
		catch (RuntimeException e) {
			throw new DaoException(e);
		}
		finally {
			if (log.isDebugEnabled()) {
				log.debug("selectWithDataHandler [" + statement + "] took " + watch);
			}
		}
	}

	/**
	 * 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 DaoException If an error occurs.
	 */
	public int selectWithDataHandler(String statement, Object parameter, 
			int offset, int limit,
			DataHandler dataHandler) throws DaoException {
		StopWatch watch = new StopWatch(true);
		try {
			MyBatisResultHandler rh = new MyBatisResultHandler(dataHandler);
			getSqlSession().select(statement, parameter, new RowBounds(offset, limit < 1 ? RowBounds.NO_ROW_LIMIT : limit), rh);
			return rh.getCount();
		} 
		catch (RuntimeException e) {
			throw new DaoException(e);
		}
		finally {
			if (log.isDebugEnabled()) {
				log.debug("selectWithDataHandler [" + statement + "] took " + watch);
			}
		}
	}
}
