package eu.dnetlib.dlms.jdbc;

import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.SQLException;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Required;

import eu.dnetlib.dlms.jdbc.server.AbstractEngineFactory;
import eu.dnetlib.dlms.jdbc.server.EngineContext;

/**
 * DataSource implementation.
 * 
 * @author lexis
 */
public class DorotyDataSource implements DataSource {

	/** Logger required by the Jdbc interface definition. */
	private PrintWriter logWriter;
	/** A description of this data source. */
	private String description;
	/** The user's account name . */
	private String user;
	/** The user's password. */
	private String password;
	/** The port number where a server is listening for requests. */
	private int portNumber;
	/** The database server name. */
	private String serverName;
	/** Network protocol. */
	private String networkProtocol;
	/**
	 * Factory used to create DOLEngine instances to pass to Connection created form this DataSource.
	 */
	private AbstractEngineFactory dolEngineFactory;
	/**
	 * Maximum time in seconds that this data source can wait while attempting to connect to a database.
	 * 
	 * @see javax.sql.CommonDataSource#getLoginTimeout()
	 */
	private int loginTimeout;

	/**
	 * Factory that connections created from this DataSource should pass to their statement for the creation of
	 * ResultSet.
	 */
	private AbstractResultSetFactory resultSetFactory;

	/**
	 * No args constructor.
	 */
	DorotyDataSource() {
	}

	/**
	 * Constructor.
	 * 
	 * @param protocol
	 *            network protocol.
	 * @param server
	 *            serverName. Can be the name of the host server or its IP address
	 * @param port
	 *            port number.
	 * @param usr
	 *            user name. Can be null if no authentication is required.
	 * @param pwd
	 *            user's password. Can be null if no authentication is required.
	 */
	public DorotyDataSource(final String protocol, final String server, final int port, final String usr, final String pwd) {
		this.networkProtocol = protocol;
		this.serverName = server;
		this.portNumber = port;
		this.user = usr;
		this.password = pwd;
	}

	@Required
	public void setResultSetFactory(final AbstractResultSetFactory resultSetFactory) {
		this.resultSetFactory = resultSetFactory;
	}

	public AbstractResultSetFactory getResultSetFactory() {
		return this.resultSetFactory;
	}

	@Required
	public void setDolEngineFactory(final AbstractEngineFactory dolEngineFactory) {
		this.dolEngineFactory = dolEngineFactory;
	}

	public AbstractEngineFactory getDolEngineFactory() {
		return this.dolEngineFactory;
	}

	/**
	 * Checks if the server is local or not on the basis of the value of the serverName field. If it is a
	 * DorotyLocalConnection is returned by the getConnection() method.
	 * 
	 * @return true if the server is local, false otherwise
	 * @throws UnknownHostException
	 *             exception retrieven the localhost's name and address.
	 */
	private boolean isLocal() throws UnknownHostException {
		String localHostName = InetAddress.getLocalHost().getHostName();
		String localHostIP = InetAddress.getLocalHost().getHostAddress();
		return (this.serverName.equalsIgnoreCase(localHostName) || this.serverName.equalsIgnoreCase(localHostIP)
				|| this.serverName.equalsIgnoreCase("localhost") || this.serverName.equalsIgnoreCase("127.0.0.1"));
	}

	public String getDescription() {
		return this.description;
	}

	public void setDescription(final String description) {
		this.description = description;
	}

	public String getUser() {
		return this.user;
	}

	public void setUser(final String user) {
		this.user = user;
	}

	public String getPassword() {
		return this.password;
	}

	public void setPassword(final String password) {
		this.password = password;
	}

	public int getPortNumber() {
		return this.portNumber;
	}

	public void setPortNumber(final int portNumber) {
		this.portNumber = portNumber;
	}

	public String getServerName() {
		return this.serverName;
	}

	public void setServerName(final String serverName) {
		this.serverName = serverName;
	}

	public void setNetworkProtocol(final String networkProtocol) {
		this.networkProtocol = networkProtocol;
	}

	public String getNetworkProtocol() {
		return this.networkProtocol;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see javax.sql.DataSource#getConnection()
	 */
	public DorotyConnection getConnection() throws SQLException {
		DorotyConnection dorCon = null;
		try {
			if (this.isLocal()) {
				dorCon = new DorotyConnection();
				dorCon.setResultSetFactory(this.resultSetFactory);
			} else {
				dorCon = this.createRemoteConnection();
			}
		} catch (UnknownHostException e) {
			//Cannot get info about the local machine... guess we can act like if we need a remote connection
			dorCon = this.createRemoteConnection();
		}
		EngineContext cxt = new EngineContext();
		//TODO: what should I put into an EngineContext
		dorCon.setDolEngine(this.dolEngineFactory.createEngine(cxt));
		return dorCon;
	}

	/**
	 * Creates a remote DorotyConnection instance that clients can use to work with this datasource.
	 * 
	 * @return a DorotyConnection to a remote doroty server
	 * @throws SQLException
	 *             connection error
	 */
	private DorotyConnection createRemoteConnection() throws SQLException {
		// TODO: create a RemoteConnection and return it!
		this.logWriter.write("A non local connection has not been implemented yet!");
		throw new SQLException("A non local connection has not been implemented yet!");
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String)
	 */
	public Connection getConnection(final String userName, final String pwd) throws SQLException {
		this.user = userName;
		this.password = pwd;
		return this.getConnection();
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see javax.sql.CommonDataSource#getLogWriter()
	 */
	public PrintWriter getLogWriter() throws SQLException {
		return this.logWriter;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see javax.sql.CommonDataSource#getLoginTimeout()
	 */
	public int getLoginTimeout() throws SQLException {
		return this.loginTimeout;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see javax.sql.CommonDataSource#setLogWriter(java.io.PrintWriter)
	 */
	public void setLogWriter(final PrintWriter out) throws SQLException {
		this.logWriter = out;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see javax.sql.CommonDataSource#setLoginTimeout(int)
	 */
	public void setLoginTimeout(final int loginTimeout) throws SQLException {
		this.loginTimeout = loginTimeout;
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Wrapper#isWrapperFor(java.lang.Class)
	 */
	public boolean isWrapperFor(final Class<?> iface) throws SQLException {
		throw new SQLException("parameter iface was class: " + iface.getName());
	}

	/**
	 * {@inheritDoc}
	 * 
	 * @see java.sql.Wrapper#unwrap(java.lang.Class)
	 */
	public <T> T unwrap(final Class<T> iface) throws SQLException {
		throw new SQLException("parameter iface was class: " + iface.getName());
	}

}
