/**
 * Copyright (c) 2014, the Railo Company Ltd.
 * Copyright (c) 2015, Lucee Assosication Switzerland
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package lucee.runtime.db;

import java.sql.Connection;
import java.sql.SQLException;

import lucee.commons.io.log.Log;
import lucee.commons.io.log.LogUtil;
import lucee.commons.lang.ExceptionUtil;
import lucee.commons.lang.types.RefInteger;
import lucee.commons.lang.types.RefIntegerImpl;
import lucee.runtime.engine.ThreadLocalPageContext;

class DCStack {

	private Item item;
	private DataSource datasource;
	private String user;
	private String pass;
	private final RefInteger counter;

	DCStack(DataSource datasource, String user, String pass) {
		this.datasource = datasource;
		this.user = user;
		this.pass = pass;
		this.counter = new RefIntegerImpl(0);
	}

	public DataSource getDatasource() {
		return datasource;
	}

	public String getUsername() {
		return user;
	}

	public String getPassword() {
		return pass;
	}

	public void add(DatasourceConnection dc) {
		// make sure the connection is not already in stack, this can happen when the conn is released twice
		Item test = item;
		while (test != null) {
			if (test.dc == dc) {
				LogUtil.log(ThreadLocalPageContext.getConfig(), Log.LEVEL_INFO, DCStack.class.getName(), "a datasource connection was released twice!");
				return;
			}
			test = test.prev;
		}

		item = new Item(item, dc);
	}

	public DatasourceConnection get() {
		if (item == null) return null;
		DatasourceConnection rtn = item.dc;
		item = item.prev;
		try {

			if (!rtn.getConnection().isClosed()) {
				return rtn;
			}
			return get();
		}
		catch (SQLException e) {}
		return null;
	}

	public boolean isEmpty() {
		return item == null;
	}

	public int size() {
		int count = 0;
		Item i = item;
		while (i != null) {
			count++;
			i = i.prev;
		}
		return count;
	}

	public int openConnectionsIn() {
		int count = 0;
		Item i = item;
		while (i != null) {
			try {
				if (!i.dc.getConnection().isClosed()) count++;
			}
			catch (SQLException e) {}
			i = i.prev;
		}
		return count;
	}

	public int openConnectionsOut() {
		return counter.toInt();
	}

	public int openConnections() {
		return openConnectionsIn() + openConnectionsOut();
	}

	class Item {
		private DatasourceConnection dc;
		private Item prev;
		private int count = 1;

		public Item(Item item, DatasourceConnection dc) {
			this.prev = item;
			this.dc = dc;
			if (prev != null) count = prev.count + 1;
		}

		@Override
		public String toString() {
			return "(" + prev + ")<-" + count;
		}
	}

	public synchronized void clear(boolean force) {
		clear(item, null, force);
	}

	/**
	 * 
	 * @param current
	 * @param next
	 * @param timeout timeout in seconds used to validate existing connections
	 * @throws SQLException
	 */
	private void clear(Item current, Item next, boolean force) {
		if (current == null) return;

		// timeout or closed
		if (force || current.dc.isTimeout() || current.dc.isLifecycleTimeout() || isClosedEL(current.dc.getConnection())
				|| Boolean.FALSE.equals(isValidEL(current.dc.getConnection()))) {

			// when timeout was reached but it is still open, close it
			if (!isClosedEL(current.dc.getConnection())) {
				try {
					current.dc.close();
				}
				catch (SQLException e) {}
			}

			// remove this connection from chain
			if (next == null) item = current.prev;
			else {
				next.prev = current.prev;
			}

			clear(current.prev, next, force);
		}
		else clear(current.prev, current, force);

		counter.setValue(0);
	}

	private boolean isClosedEL(Connection conn) {
		try {
			return conn.isClosed();
		}
		catch (SQLException se) {
			datasource.getLog().error("Connection  Pool", se);
			// in case of a exception we see this conn as useless and close the connection
			try {
				conn.close();
			}
			catch (SQLException e) {
				datasource.getLog().error("Connection  Pool", e);
			}

			return true;
		}
	}

	private Boolean isValidEL(Connection conn) {
		try {
			return conn.isValid(datasource.getNetworkTimeout()) ? Boolean.TRUE : Boolean.FALSE;
		}
		catch (Throwable t) {
			ExceptionUtil.rethrowIfNecessary(t);
			return null;
		}
	}

	public RefInteger getCounter() {
		return counter;
	}
}
@@ -941,49 +941,8 @@
 il;%0A
-import lucee.commons.lang.ExceptionUtil;%0A
 impo
@@ -981,16 +981,16 @@
 nteger;%0A
+
 import l
@@ -2610,35 +2610,32 @@
 %0A%09%09%09%7D%0A%09%09%09catch (
-SQL
 Exception e) %7B%7D%0A
@@ -3841,35 +3841,32 @@
 %09%09%09%7D%0A%09%09%09%09catch (
-SQL
 Exception e) %7B%7D%0A
@@ -4199,35 +4199,32 @@
 );%0A%09%09%7D%0A%09%09catch (
-SQL
 Exception se) %7B%0A
@@ -4661,62 +4661,22 @@
 ch (
-Throwable t) %7B%0A%09%09%09ExceptionUtil.rethrowIfNecessary(t);
+Exception e) %7B
 %0A%09%09%09

clean up