package eu.dnetlib.dlms;

import java.net.MalformedURLException;
import java.net.URL;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.text.DateFormat;
import java.text.ParseException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;

import javax.annotation.Resource;
import javax.xml.ws.Endpoint;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;

import eu.dnetlib.dlms.jdbc.InformationObjectImpl;
import eu.dnetlib.dlms.jdbc.serialization.ItemWrapper;
import eu.dnetlib.dlms.jdbc.serialization.RSMetadataWrapper;
import eu.dnetlib.dlms.jdbc.server.ConnectionWrapper;
import eu.dnetlib.dlms.jdbc.server.ConnectionWrapperFactory;
import eu.dnetlib.dlms.jdbc.server.ResultSetWrapper;
import eu.dnetlib.dlms.rmi.DLMSBrokenConnectionException;
import eu.dnetlib.dlms.rmi.DLMSConnectionException;
import eu.dnetlib.dlms.rmi.DLMSConnectionService;
import eu.dnetlib.dlms.rmi.ParameterDescription;
import eu.dnetlib.dlms.rmi.StatementDescription;
import eu.dnetlib.enabling.resultset.ResultSetFactory;
import eu.dnetlib.enabling.tools.AbstractBaseService;
import eu.dnetlib.enabling.tools.UniqueIdentifierGenerator;
import eu.dnetlib.enabling.tools.UniqueIdentifierGeneratorImpl;
import eu.dnetlib.miscutils.jaxb.JaxbFactory;
import eu.dnetlib.soap.EndpointReferenceBuilder;

public class DLMSConnectionServiceImpl extends AbstractBaseService implements DLMSConnectionService {

	private static final Log log = LogFactory.getLog(DLMSConnectionServiceImpl.class); // NOPMD by marko on 11/24/08 5:02 PM

	private Map<String, ConnectionWrapper> connectionMap = new HashMap<String, ConnectionWrapper>();

	private UniqueIdentifierGenerator uuidGenerator = new UniqueIdentifierGeneratorImpl();

	private ConnectionWrapperFactory connectionFactory;

	private Endpoint endpoint;

	@Resource(name = "jaxwsEndpointReferenceBuilder")
	private EndpointReferenceBuilder<Endpoint> eprBuilder;

	/**
	 * jaxb itemWrapper factory.
	 */
	private JaxbFactory<ItemWrapper> itemFactory;
	/**
	 * jaxb RSMetadataWrapper factory.
	 */
	private JaxbFactory<RSMetadataWrapper> metadataFactory;

	/**
	 * pull result set factory.
	 */
	private ResultSetFactory resultSetFactory;

	public W3CEndpointReference createConnection() {
		try {
			final String id = this.uuidGenerator.generateIdentifier();
			this.connectionMap.put(id, this.connectionFactory.newInstance());

			return this.eprBuilder.getEndpointReference(this.endpoint, id);
		} catch (final RuntimeException e) {
			log.fatal("failing to create connection", e);
			throw e;
		}

	}

	public W3CEndpointReference executeStatement(final String connectionId, final StatementDescription statement) throws DLMSConnectionException,
			DLMSBrokenConnectionException {
		log.info("Executing statement " + statement.getSource() + " in connection " + connectionId);
		log.info("positional args: " + statement.getPositionalParameters());
		log.info("named args: " + statement.getNamedParameters());

		final ConnectionWrapper connection = this.connectionMap.get(connectionId);
		if (connection == null)
			throw new DLMSBrokenConnectionException();

		try {
			final PreparedStatement stm = connection.getConnection().prepareStatement(statement.getSource());

			if (statement.getPositionalParameters() != null)
				this.populateParameters(stm, statement.getPositionalParameters());

			stm.execute();
			log.info("ResultSetFactory class : " + this.resultSetFactory.getClass());
			log.info("Statement class : " + stm.getClass() + " with result set " + stm.getResultSet());
			return this.resultSetFactory.createResultSet(new ResultSetWrapper(stm.getResultSet(), this.itemFactory, this.metadataFactory));
		} catch (final SQLException e) {
			log.fatal("cannot execute statement", e);
			throw new IllegalStateException("cannot execute statement", e);
		} catch (final RuntimeException e) {
			log.fatal("cannot execute statement, runtime ex:", e);
			throw e;
		}

	}

	private void populateParameters(final PreparedStatement stm, final List<ParameterDescription> positionalParameters) throws SQLException {
		int i = 1;
		for (final ParameterDescription par : positionalParameters)
			this.populateParameter(stm, par, i++);
	}

	private void populateParameter(final PreparedStatement stm, final ParameterDescription par, final int i) throws SQLException {
		log.info("SETTING PARAM " + i + " in " + stm);

		if (par.getType().equals("STRING"))
			stm.setString(i, par.getValue());
		else if (par.getType().equals("INT"))
			stm.setInt(i, Integer.parseInt(par.getValue()));
		else if (par.getType().equals("DATE"))
			stm.setDate(i, this.parseDate(par.getValue()));
		else if (par.getType().equals("INFORMATION_OBJECT"))
			stm.setObject(i, new InformationObjectImpl(par.getValue()));
		else if (par.getType().equals("BOOLEAN"))
			stm.setBoolean(i, Boolean.parseBoolean(par.getValue()));
		else if (par.getType().equals("URL"))
			try {
				stm.setURL(i, new URL(par.getValue()));
			} catch (final MalformedURLException e) {
				throw new IllegalArgumentException(par.getValue() + " is not a valid URL");
			}
		else
			throw new IllegalArgumentException("Unhandled type: " + par.getType());

	}

	private Date parseDate(final String value) {
		final DateFormat df = DateFormat.getDateInstance();
		df.setTimeZone(TimeZone.getTimeZone("GMT"));

		try {
			final java.util.Date tmp = df.parse(value);
			return new java.sql.Date(tmp.getTime());
		} catch (final ParseException e) {
			throw new IllegalArgumentException("invalid date format " + value);
		}

	}

	public ConnectionWrapperFactory getConnectionFactory() {
		return this.connectionFactory;
	}

	@Required
	public void setConnectionFactory(final ConnectionWrapperFactory connectionFactory) {
		this.connectionFactory = connectionFactory;
	}

	public UniqueIdentifierGenerator getUuidGenerator() {
		return this.uuidGenerator;
	}

	public void setUuidGenerator(final UniqueIdentifierGenerator uuidGenerator) {
		this.uuidGenerator = uuidGenerator;
	}

	public Endpoint getEndpoint() {
		return this.endpoint;
	}

	@Required
	public void setEndpoint(final Endpoint endpoint) {
		this.endpoint = endpoint;
	}

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

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

	public JaxbFactory<ItemWrapper> getItemFactory() {
		return this.itemFactory;
	}

	@Required
	public void setItemFactory(final JaxbFactory<ItemWrapper> itemFactory) {
		this.itemFactory = itemFactory;
	}

	public void setMetadataFactory(final JaxbFactory<RSMetadataWrapper> metadataFactory) {
		this.metadataFactory = metadataFactory;
	}

	public JaxbFactory<RSMetadataWrapper> getMetadataFactory() {
		return this.metadataFactory;
	}

}
