package eu.dnetlib.enabling.resultset.client;

import java.util.Map;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.wsaddressing.W3CEndpointReference;

import eu.dnetlib.enabling.resultset.client.utils.EPRUtils;
import eu.dnetlib.enabling.resultset.rmi.ResultSetService;
import eu.dnetlib.enabling.tools.ServiceResolver;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;

import org.apache.cxf.transport.http.HTTPConduit;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.springframework.beans.factory.annotation.Required;

/**
 * 
 * @author claudio
 * 
 */
public class ResultSetClientFactory implements ResultSetClient {

	/**
	 * logger
	 */
	private static final Log log = LogFactory.getLog(ResultSetClientFactory.class);

	private final static long DEFAULT_CONNECT_TIMEOUT = 10000;

	private final static long DEFAULT_REQUEST_TIMEOUT = 60000;

	private final static int DEFAULT_PAGE_SIZE = 100;

	/**
	 * used to resolve the epr references to the service endpoint
	 */
	private ServiceResolver serviceResolver;

	/**
	 * utility object
	 */
	private EPRUtils eprUtils;

	/**
	 * actual page size
	 */
	private int pageSize;

	/**
	 * request timeout
	 */
	private long timeout;

	/**
	 * request timeout
	 */
	private long connectTimeout;

	public ResultSetClientFactory() {
		this(DEFAULT_PAGE_SIZE, DEFAULT_REQUEST_TIMEOUT, DEFAULT_CONNECT_TIMEOUT);
	}

	/**
	 * 
	 * @param pageSize
	 * @param timeout
	 * @throws IllegalArgumentException
	 */
	public ResultSetClientFactory(int pageSize, long timeout) throws IllegalArgumentException {
		this(pageSize, timeout, DEFAULT_CONNECT_TIMEOUT);
	}

	/**
	 *
	 * @param pageSize
	 * @param timeout
	 *          time to wait for server response before throwing a timeout exception
	 * @param connectTimeout
	 *          time to wait for server to accept the connection before throwing a connection timeout exception
	 * @throws IllegalArgumentException
	 */
	public ResultSetClientFactory(int pageSize, long timeout, long connectTimeout) throws IllegalArgumentException {
		if (pageSize <= 0 || timeout <= 0 || connectTimeout <= 0) {
			throw new IllegalArgumentException("parameters pageSize, timeout and connectTimeout must be greater than zero");
		}
		log.info(String.format("creating new ResultSetClientIterableFactory with pageSize (%s), read timeout (%s) and connect timeout (%s)",
				pageSize, timeout, connectTimeout));
		this.pageSize = pageSize;
		this.timeout = timeout;
		this.connectTimeout = connectTimeout;
	}

	/**
	 * 
	 * @param epr
	 * @param pageSize
	 * @return
	 */
	@Override
	public IterableResultSetClient getClient(W3CEndpointReference epr, int pageSize) {
		final ResultSetService resultSet = getResultSetService(epr, getConnectTimeout(), getTimeout());
		final String rsId = serviceResolver.getResourceIdentifier(epr);

		//using given pageSize and default timeout
		return new IterableResultSetClient(resultSet, rsId, pageSize, getTimeout());
	}

	/**
	 * 
	 * @param epr
	 * @return
	 */
	@Override
	public IterableResultSetClient getClient(W3CEndpointReference epr) {
		final ResultSetService resultSet = getResultSetService(epr, getConnectTimeout(), getTimeout());
		final String rsId = serviceResolver.getResourceIdentifier(epr);

		//using default pageSize and timeouts
		return new IterableResultSetClient(resultSet, rsId, getPageSize(), getTimeout());
	}

	private ResultSetService getResultSetService(final W3CEndpointReference epr, final long connectTimeout, final long requestTimeout) {
		final ResultSetService service = serviceResolver.getService(ResultSetService.class, epr);

		log.debug(String.format("creting resultSet service stub (%s) with connectTimeout(%s), requestTimeout(%s)", service.getClass().getName(), connectTimeout, requestTimeout));

		if(service instanceof Client) {
			log.debug(String.format("setting timeouts for %s", Client.class));
			final Client client = ClientProxy.getClient(service);
			final HTTPConduit http = (HTTPConduit) client.getConduit();
			final HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();

			httpClientPolicy.setConnectionTimeout(connectTimeout);
			httpClientPolicy.setAllowChunking(false);
			httpClientPolicy.setReceiveTimeout(requestTimeout);

			http.setClient(httpClientPolicy);
		} else if (service instanceof BindingProvider) {
			log.debug(String.format("setting timeouts for %s", BindingProvider.class));
			final Map<String, Object> requestContext = ((BindingProvider) service).getRequestContext();

			// can't be sure about which will be used. Set them all.
			requestContext.put("com.sun.xml.internal.ws.request.timeout", requestTimeout);
			requestContext.put("com.sun.xml.internal.ws.connect.timeout", connectTimeout);

			requestContext.put("com.sun.xml.ws.request.timeout", requestTimeout);
			requestContext.put("com.sun.xml.ws.connect.timeout", connectTimeout);

			requestContext.put("javax.xml.ws.client.receiveTimeout", requestTimeout);
			requestContext.put("javax.xml.ws.client.connectionTimeout", connectTimeout);
		}

		return service;
	}

	/**
	 * 
	 * @param stringEpr
	 * @param pageSize
	 * @return
	 */
	@Override
	public IterableResultSetClient getClient(String stringEpr, int pageSize) {
		return getClient(eprUtils.getEpr(stringEpr), pageSize);
	}

	/**
	 * 
	 * @param stringEpr
	 * @return
	 */
	@Override
	public IterableResultSetClient getClient(String stringEpr) {
		return getClient(eprUtils.getEpr(stringEpr));
	}

	@Required
	public void setServiceResolver(ServiceResolver serviceResolver) {
		this.serviceResolver = serviceResolver;
	}

	@Required
	public void setEprUtils(EPRUtils eprUtils) {
		this.eprUtils = eprUtils;
	}

	public int getPageSize() {
		return pageSize;
	}

	@Required
	public void setPageSize(int pageSize) {
		this.pageSize = pageSize;
	}

	public long getTimeout() {
		return timeout;
	}

	@Required
	public void setTimeout(long timeout) {
		this.timeout = timeout;
	}

	public long getConnectTimeout() {
		return connectTimeout;
	}

	public void setConnectTimeout(final long connectTimeout) {
		this.connectTimeout = connectTimeout;
	}
}
