/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package woolpack.sql.tx;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

import woolpack.sql.adapter.ConnectionAdapter;
import woolpack.sql.adapter.PreparedStatementAdapter;
import woolpack.sql.adapter.ResultSetAdapter;
import woolpack.sql.adapter.StatementAdapter;

class TxContext {
	private Connection connection;
	private List<TxTemporaryCloser> list;
	private boolean commitFlag;
	private boolean rollbackFlag;
	
	void commit() {
		commitFlag = true;
	}
	boolean isCommit() {
		return commitFlag;
	}
	void rollback() {
		rollbackFlag = true;
	}
	boolean isRollback() {
		return rollbackFlag;
	}
	
	Connection getConnection() {
		return connection;
	}
	
	void setConnection(final Connection connection) {
		this.connection = connection;
		rollbackFlag = false;
		commitFlag = false;
		list = new ArrayList<TxTemporaryCloser>();
	}
	
	void closeAll() throws SQLException {
		SQLException e0 = null;
		while(!list.isEmpty()) {
			try {
				list.get(list.size() - 1).close();
			} catch (final SQLException e1) {
				if (e0 == null) {
					e0 = e1;
				}
			}
		}
		if (e0 != null) {
			throw e0;
		}
	}
	private void remove(final Object o) throws SQLException {
		for (int i=list.size() - 1; i >= 0; i--) {
			final TxTemporaryCloser closer = list.get(i);
			if (closer.getObject() == o) {
				list.remove(i);
				break;
			}
		}
	}
	
	Connection newTmpConnection() {
		return new ConnectionAdapter(connection) {
			@Override
			public void commit() throws SQLException {
				TxContext.this.commit();
			}
			@Override
			public void rollback() throws SQLException {
				TxContext.this.rollback();
			}
			@Override
			public void close() throws SQLException {
				// nothing to do.
			}
			@Override
			public Statement createStatement() throws SQLException {
				return newStatement(super.createStatement());
			}
			@Override
			public Statement createStatement(final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException {
				return newStatement(super.createStatement(resultSetType, resultSetConcurrency, resultSetHoldability));
			}
			@Override
			public Statement createStatement(final int resultSetType, final int resultSetConcurrency) throws SQLException {
				return newStatement(super.createStatement(resultSetType, resultSetConcurrency));
			}
			@Override
			public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency, final int resultSetHoldability) throws SQLException {
				return newPreparedStatement(super.prepareStatement(sql, resultSetType, resultSetConcurrency, resultSetHoldability));
			}
			@Override
			public PreparedStatement prepareStatement(final String sql, final int resultSetType, final int resultSetConcurrency) throws SQLException {
				return newPreparedStatement(super.prepareStatement(sql, resultSetType, resultSetConcurrency));
			}
			@Override
			public PreparedStatement prepareStatement(final String sql, final int autoGeneratedKeys) throws SQLException {
				return newPreparedStatement(super.prepareStatement(sql, autoGeneratedKeys));
			}
			@Override
			public PreparedStatement prepareStatement(final String sql, final int[] columnIndexes) throws SQLException {
				return newPreparedStatement(super.prepareStatement(sql, columnIndexes));
			}
			@Override
			public PreparedStatement prepareStatement(final String sql, final String[] columnNames) throws SQLException {
				return newPreparedStatement(super.prepareStatement(sql, columnNames));
			}
			@Override
			public PreparedStatement prepareStatement(final String sql) throws SQLException {
				return newPreparedStatement(super.prepareStatement(sql));
			}
		};
	}
	
	private Statement newStatement(final Statement parent) {
		final Statement s = new StatementAdapter(parent) {
			@Override
			public void close() throws SQLException {
				try {
					super.close();
				} finally {
					remove(this);
				}
			}
			@Override
			public ResultSet executeQuery(final String sql) throws SQLException {
				return newResultSet(super.executeQuery(sql), this);
			}
			@Override
			public ResultSet getGeneratedKeys() throws SQLException {
				return newResultSet(super.getGeneratedKeys(), this);
			}
			@Override
			public ResultSet getResultSet() throws SQLException {
				return newResultSet(super.getResultSet(), this);
			}
		};
		list.add(new TxTemporaryCloser(s) {
			@Override
			void close() throws SQLException {
				s.close();
			}
		});
		return s;
	}
	
	private PreparedStatement newPreparedStatement(final PreparedStatement parent) {
		final PreparedStatement s = new PreparedStatementAdapter(parent) {
			@Override
			public void close() throws SQLException {
				try {
					super.close();
				} finally {
					remove(this);
				}
			}
			@Override
			public ResultSet executeQuery() throws SQLException {
				return newResultSet(super.executeQuery(), this);
			}
			@Override
			public ResultSet executeQuery(final String sql) throws SQLException {
				return newResultSet(super.executeQuery(sql), this);
			}
			@Override
			public ResultSet getGeneratedKeys() throws SQLException {
				return newResultSet(super.getGeneratedKeys(), this);
			}
			@Override
			public ResultSet getResultSet() throws SQLException {
				return newResultSet(super.getResultSet(), this);
			}
		};
		list.add(new TxTemporaryCloser(s) {
			@Override
			void close() throws SQLException {
				s.close();
			}
		});
		return s;
	}

	private ResultSet newResultSet(
			final ResultSet parent,
			final Statement statement) {
		final ResultSet s = new ResultSetAdapter(parent) {
			@Override
			public void close() throws SQLException {
				try {
					super.close();
				} finally {
					remove(this);
				}
			}
			@Override
			public Statement getStatement() throws SQLException {
				return statement;
			}
		};
		list.add(new TxTemporaryCloser(s) {
			@Override
			void close() throws SQLException {
				s.close();
			}
		});
		return s;
	}
}
