/*******************************************************************************
 * Copyright (c) 2010 Robert "Unlogic" Olofsson (unlogic@unlogic.se).
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v3
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/lgpl-3.0-standalone.html
 ******************************************************************************/
package se.unlogic.standardutils.dao;

import se.unlogic.standardutils.dao.querys.ArrayListQuery;
import se.unlogic.standardutils.dao.querys.BooleanQuery;
import se.unlogic.standardutils.dao.querys.HashMapQuery;
import se.unlogic.standardutils.dao.querys.ObjectQuery;
import se.unlogic.standardutils.dao.querys.PreparedStatementQuery;
import se.unlogic.standardutils.dao.querys.UpdateQuery;
import se.unlogic.standardutils.db.DBUtils;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Map.Entry;

public class TransactionHandler {

	private final Connection connection;
	private ArrayList<PreparedStatementQuery> queryList;
	private boolean aborted;
	private boolean commited;

	public TransactionHandler(DataSource dataSource) throws SQLException {

		super();
		this.connection = dataSource.getConnection();
		connection.setAutoCommit(false);
	}

	public TransactionHandler(Connection connection) throws SQLException {

		super();
		this.connection = connection;
		connection.setAutoCommit(false);
	}

	public UpdateQuery getUpdateQuery(String sqlExpression) throws SQLException {

		this.checkStatus();

		UpdateQuery query = new UpdateQuery(connection, false, sqlExpression);

		checkQueryList();
		
		this.queryList.add(query);

		return query;
	}

	private synchronized void checkQueryList() {

		if(queryList == null){
			
			queryList = new ArrayList<PreparedStatementQuery>();
		}
		
	}

	public BooleanQuery getBooleanQuery(String sql) throws SQLException {

		BooleanQuery query = new BooleanQuery(connection, false, sql);
		
		checkQueryList();
		
		this.queryList.add(query);
		
		return query;
	}

	public <T> ObjectQuery<T> getObjectQuery(String sql, BeanResultSetPopulator<T> populator) throws SQLException {

		ObjectQuery<T> query = new ObjectQuery<T>(connection, false, sql, populator);
		
		checkQueryList();
		
		this.queryList.add(query);
		
		return query;
	}

	public <T> ArrayListQuery<T> getArrayListQuery(String sql, BeanResultSetPopulator<T> populator) throws SQLException {

		ArrayListQuery<T> query = new ArrayListQuery<T>(connection, false, sql, populator);
		
		checkQueryList();
		
		this.queryList.add(query);
		
		return query;
	}

	public <K, V> HashMapQuery<K, V> getHashMapQuery(String sql, BeanResultSetPopulator<? extends Entry<K, V>> populator) throws SQLException {

		HashMapQuery<K, V> query = new HashMapQuery<K, V>(connection, false, sql, populator);
		
		checkQueryList();
		
		this.queryList.add(query);
		
		return query;
	}

	public synchronized void commit() throws SQLException {

		this.checkStatus();

		try {
			connection.commit();
			this.commited = true;
		} finally {

			if (!this.commited) {
				this.abort();
			} else {
				this.closeConnection();
			}
		}
	}

	public synchronized int getQueryCount() {

		if(queryList == null){
			
			return 0;
		}
		
		return this.queryList.size();
	}

	public synchronized void abort() {

		this.checkStatus();

		if(queryList != null){
			for (PreparedStatementQuery query : queryList) {
				query.abort();
			}
		}
		
		if (connection != null) {
			try {
				connection.rollback();
			} catch (SQLException e) {}
		}

		this.closeConnection();
		this.aborted = true;
	}

	private void closeConnection() {

		DBUtils.closeConnection(connection);
	}

	private void checkStatus() {

		if (aborted) {
			throw new TransactionAlreadyAbortedException();
		} else if (commited) {
			throw new TransactionAlreadyComittedException();
		}
	}

	@Override
	protected void finalize() throws Throwable {

		if (!commited && !aborted) {
			this.abort();
		}

		super.finalize();
	}

	public boolean isClosed() {

		return commited || aborted;
	}

	public static void autoClose(TransactionHandler transactionHandler) {

		if (transactionHandler != null && !transactionHandler.isClosed()) {
			transactionHandler.abort();
		}
	}

	//Workaround for AnnotatedDAO, needs a better solution in the long run
	Connection getConnection() {

		return connection;
	}

	public boolean isAborted() {

		return aborted;
	}

	public boolean isCommited() {

		return commited;
	}
}
