/*******************************************************************************
 * Copyright (c) 1998, 2013 Oracle and/or its affiliates. All rights reserved.
 * This program and the accompanying materials are made available under the 
 * terms of the Eclipse Public License v1.0 and Eclipse Distribution License v. 1.0 
 * which accompanies this distribution. 
 * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 * and the Eclipse Distribution License is available at 
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * Contributors:
 *     dclarke - Dynamic Persistence
 *       http://wiki.eclipse.org/EclipseLink/Development/Dynamic 
 *       (https://bugs.eclipse.org/bugs/show_bug.cgi?id=200045)
 *             - initial JPA Employee example using XML (bug 217884)
 * 		         ported from earlier Oracle TopLink examples
 *     mnorman - tweaks to work from Ant command-line,
 *               get database properties from System, etc.
 *
 ******************************************************************************/
package org.eclipse.persistence.testing.tests.dynamic;

//javase imports
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

//EclipseLink imports
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.logging.DefaultSessionLog;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.logging.SessionLogEntry;
import org.eclipse.persistence.queries.DatabaseQuery;
import org.eclipse.persistence.sessions.Session;
import org.eclipse.persistence.sessions.SessionEvent;
import org.eclipse.persistence.sessions.SessionEventAdapter;

/**
 * 
 * @author dclarke
 * @since EclipseLink 1.1.2
 */
public class QuerySQLTracker extends SessionEventAdapter {
	private List<QueryResult> queries;

	/**
	 * Constructs and installs the event listener and sql tracking session log
	 * 
	 * @param session
	 */
	private QuerySQLTracker(Session session) {
		session.getEventManager().addListener(this);
		session.setSessionLog(new SQLTrackingSessionLog(session, this));
		reset();
	}

	public static QuerySQLTracker install(Session session) {
		if (session.getSessionLog() instanceof SQLTrackingSessionLog) {
			return ((SQLTrackingSessionLog) session.getSessionLog())
					.getTracker();
		}
		return new QuerySQLTracker(session);
	}

	/**
	 * Helper method to retrieve a tracker from a session where it was installed
	 * If the session exists but does not have a tracler installed then an
	 * exception is thrown.
	 */
	public static QuerySQLTracker getTracker(Session session) {
		if (session == null) {
			return null;
		}
		SessionLog sessionLog = session.getSessionLog();

		if (sessionLog instanceof QuerySQLTracker.SQLTrackingSessionLog) {
			return ((QuerySQLTracker.SQLTrackingSessionLog) sessionLog)
					.getTracker();
		}
		throw new RuntimeException(
				"Could not retireve QuerySQLTracke from session: " + session);
	}

	/**
	 * Reset the lists of SQL and queries being tracked
	 */
	public void reset() {
		this.queries = new ArrayList<QueryResult>();
	}

	public List<QueryResult> getQueries() {
		return this.queries;
	}

	protected QuerySQLTracker.QueryResult getCurrentResult() {
		if (getQueries().size() == 0) {
			getQueries().add(new QueryResult(null));
			// throw new RuntimeException("Received SQL without a Query ???");
		}
		return getQueries().get(getQueries().size() - 1);
	}

	public int getTotalSQLCalls() {
		int totalSQLCalls = 0;

		for (QueryResult result : getQueries()) {
			totalSQLCalls += result.sqlStatements.size();
		}

		return totalSQLCalls;
	}

	public int getTotalSQLCalls(String startsWith) {
		int sqlCalls = 0;

		for (QueryResult result : getQueries()) {
			for (String sql : result.sqlStatements) {
				String sub = sql.substring(0, startsWith.length());
				if (sub.equalsIgnoreCase(startsWith)) {
					sqlCalls++;
				}
			}
		}

		return sqlCalls;
	}

	public int getTotalSQLSELECTCalls() {
		return getTotalSQLCalls("SELECT");
	}

	public int getTotalSQLINSERTCalls() {
		return getTotalSQLCalls("INSERT");
	}

	public int getTotalSQLUPDATECalls() {
		return getTotalSQLCalls("UPDATE");
	}

	public int getTotalSQLDELETECalls() {
		return getTotalSQLCalls("DELETE");
	}

	public void preExecuteQuery(SessionEvent event) {
		//System.err.println("*** QuerySQLTracker.preExecuteQuery(" + event.getQuery() + ")");
		//Thread.dumpStack();
		QueryResult result = new QueryResult(event.getQuery());
		getQueries().add(result);
	}

	public void postExecuteQuery(SessionEvent event) {
		if (getCurrentResult().query == null) {
			getCurrentResult().setQuery(event.getQuery());
		}
		getCurrentResult().setResult(event.getResult());
	}

	protected class QueryResult {
		private DatabaseQuery query;
		private String resultString = null;
		private List<String> sqlStatements = new ArrayList<String>();

		QueryResult(DatabaseQuery q) {
			query = q;
		}

		protected void setQuery(DatabaseQuery query) {
			this.query = query;
		}

		@SuppressWarnings("unchecked")
        protected void setResult(Object queryResult) {
			StringWriter writer = new StringWriter();
			writer.write(Helper.getShortClassName(query));
			writer.write("[" + System.identityHashCode(query) + "]");
			writer.write(" result = ");

			Object result = queryResult;
			if (queryResult instanceof Collection) {
				result = ((Collection) queryResult).toArray();
			}

			if (result == null) {
				writer.write("NONE");
			} else {
				if (result instanceof Object[]) {
					Object[] results = (Object[]) result;
					writer.write("<" + results.length + "> [");
					for (int index = 0; index < results.length; index++) {
						if (index > 0) {
							writer.write(", ");
						}
						writer.write(results[index] + "");
					}
					writer.write("]");
					resultString = writer.toString();
				} else {
					writer.write(result.toString());
				}
			}

			this.resultString = writer.toString();
		}

		public void addSQL(String sql) {
			sqlStatements.add(sql);
		}

		public String toString() {
			if (this.resultString == null) {
				setResult(null);
			}
			return this.resultString;
		}
	}

	/**
	 * This custom SessionLog implementation wraps the existing one and redirects
	 * all SQL calls to the tracker. All messages are also passed to the original
	 * tracker.
	 */
	public class SQLTrackingSessionLog extends DefaultSessionLog {
	    
		private QuerySQLTracker tracker;

		private SessionLog originalLog;

		protected SQLTrackingSessionLog(Session session,
				QuerySQLTracker aTracker) {
			this.tracker = aTracker;
			this.originalLog = session.getSessionLog();
			setSession(session);
			setWriter(this.originalLog.getWriter());
		}

		public QuerySQLTracker getTracker() {
			return this.tracker;
		}

		public synchronized void log(SessionLogEntry entry) {

			if (entry.getNameSpace() != null
					&& entry.getNameSpace().equalsIgnoreCase(SessionLog.SQL)) {
				getTracker().getCurrentResult().addSQL(entry.getMessage());
			}
			super.log(entry);
		}

		@Override
		public int getLevel(String category) {
			return this.originalLog.getLevel(category);
		}

		@Override
		public void setLevel(int level, String category) {
			this.originalLog.setLevel(level, category);
		}

		@Override
		public int getLevel() {
			return this.originalLog.getLevel();
		}

		@Override
		public void setLevel(int level) {
			this.originalLog.setLevel(level);
		}

		@Override
		public boolean shouldPrintConnection() {
			return this.originalLog.shouldPrintConnection();
		}

		@Override
		public boolean shouldPrintDate() {
			return this.originalLog.shouldPrintDate();
		}

		@Override
		public boolean shouldPrintSession() {
			return this.originalLog.shouldPrintSession();
		}

		@Override
		public boolean shouldPrintThread() {
			return this.originalLog.shouldPrintThread();
		}
	}

	public void printResults(String prefix) {
		System.out.println(prefix + "-QuerySQLTracker-Queries:");

		int sql = 0;
		for (int index = 0; index < getQueries().size(); index++) {
			QueryResult result = getQueries().get(index);

			System.out.println("\t" + (index + 1) + "> " + result);

			for (int sqlNum = 0; sqlNum < result.sqlStatements.size(); sqlNum++) {
				sql++;
				System.out.println("\t\t" + (index + 1) + "." + (sqlNum + 1)
						+ "-" + sql + "> " + result.sqlStatements.get(sqlNum));
			}
		}

		System.out.println(prefix + "-QuerySQLTracker-Queries: "
				+ getQueries().size());
		System.out.println(prefix + "-QuerySQLTracker-INSERT: "
				+ getTotalSQLINSERTCalls());
		System.out.println(prefix + "-QuerySQLTracker-SELECT: "
				+ getTotalSQLSELECTCalls());
		System.out.println(prefix + "-QuerySQLTracker-UPDATE: "
				+ getTotalSQLUPDATECalls());
		System.out.println(prefix + "-QuerySQLTracker-DELETE: "
				+ getTotalSQLDELETECalls());
	}

}
