/*
 * JBoss, Home of Professional Open Source.
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 *
 * 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, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301 USA.
 */

package org.teiid.resource.adapter.ws;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;

import javax.activation.DataSource;
import javax.resource.ResourceException;
import javax.security.auth.Subject;
import javax.ws.rs.core.Response.Status;
import javax.xml.namespace.QName;
import javax.xml.ws.AsyncHandler;
import javax.xml.ws.Binding;
import javax.xml.ws.Dispatch;
import javax.xml.ws.EndpointReference;
import javax.xml.ws.Response;
import javax.xml.ws.Service;
import javax.xml.ws.Service.Mode;
import javax.xml.ws.WebServiceException;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.http.HTTPBinding;

import org.apache.cxf.Bus;
import org.apache.cxf.BusFactory;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.endpoint.Endpoint;
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.jaxrs.client.WebClient;
import org.apache.cxf.jaxws.DispatchImpl;
import org.apache.cxf.transports.http.configuration.HTTPClientPolicy;
import org.apache.cxf.ws.security.SecurityConstants;
import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.ietf.jgss.GSSCredential;
import org.teiid.OAuthCredential;
import org.teiid.core.util.ArgCheck;
import org.teiid.core.util.Base64;
import org.teiid.logging.LogConstants;
import org.teiid.logging.LogManager;
import org.teiid.logging.MessageLevel;
import org.teiid.resource.spi.BasicConnection;
import org.teiid.resource.spi.ConnectionContext;
import org.teiid.translator.WSConnection;

/**
 * WebService connection implementation.
 *
 * TODO: set a handler chain
 */
public class WSConnectionImpl extends BasicConnection implements WSConnection {

	private static final String CONNECTION_TIMEOUT = "javax.xml.ws.client.connectionTimeout"; //$NON-NLS-1$
	private static final String RECEIVE_TIMEOUT = "javax.xml.ws.client.receiveTimeout"; //$NON-NLS-1$

	private static final class HttpDataSource implements DataSource {
		private final URL url;
		private InputStream content;
		private String contentType;

		private HttpDataSource(URL url, InputStream entity, String contentType) {
			this.url = url;
			this.content = entity;
			this.contentType = contentType;
		}

		@Override
		public OutputStream getOutputStream() throws IOException {
			throw new UnsupportedOperationException();
		}

		@Override
		public String getName() {
			return this.url.getPath();
		}

		@Override
		public InputStream getInputStream() throws IOException {
			return this.content;
		}

		@Override
		public String getContentType() {
			return this.contentType;
		}
	}

	private static final class HttpDispatch implements Dispatch<DataSource> {

		private static final String AUTHORIZATION = "Authorization"; //$NON-NLS-1$
        private HashMap<String, Object> requestContext = new HashMap<String, Object>();
		private HashMap<String, Object> responseContext = new HashMap<String, Object>();
		private WebClient client;
		private String endpoint;

		public HttpDispatch(String endpoint, String configFile, @SuppressWarnings("unused") String configName) {
			this.endpoint = endpoint;
			if (configFile == null) {
			    this.client = WebClient.create(this.endpoint);
			}
			else {
			    this.client = WebClient.create(this.endpoint, configFile);
			}
		}

		@Override
		public DataSource invoke(DataSource msg) {
			try {
				final URL url = new URL(this.endpoint);
				final String httpMethod = (String)this.requestContext.get(MessageContext.HTTP_REQUEST_METHOD);
				
				Map<String, List<String>> header = (Map<String, List<String>>)this.requestContext.get(MessageContext.HTTP_REQUEST_HEADERS);
				for (Map.Entry<String, List<String>> entry : header.entrySet()) {
					this.client.header(entry.getKey(), entry.getValue().toArray());
				}
				String username = (String) this.requestContext.get(Dispatch.USERNAME_PROPERTY);
				String password = (String) this.requestContext.get(Dispatch.PASSWORD_PROPERTY);
				
				if (username != null) {
					this.client.header(AUTHORIZATION, "Basic " + Base64.encodeBytes((username + ':' + password).getBytes())); //$NON-NLS-1$
				}
				else if (this.requestContext.get(GSSCredential.class.getName()) != null) {
				    WebClient.getConfig(this.client).getRequestContext().put(GSSCredential.class.getName(), this.requestContext.get(GSSCredential.class.getName()));
				    WebClient.getConfig(this.client).getRequestContext().put("auth.spnego.requireCredDelegation", true); //$NON-NLS-1$ 
				}
                else if (this.requestContext.get(OAuthCredential.class.getName()) != null) {
                    OAuthCredential credential = (OAuthCredential)this.requestContext.get(OAuthCredential.class.getName());                    
                    this.client.header(AUTHORIZATION, credential.getAuthorizationHeader(this.endpoint, httpMethod));
                }
				
				InputStream payload = null;
				if (msg != null) {
					payload = msg.getInputStream();
				}
				
				HTTPClientPolicy clientPolicy = WebClient.getConfig(this.client).getHttpConduit().getClient();
				Long timeout = (Long) this.requestContext.get(RECEIVE_TIMEOUT); 
				if (timeout != null) {
					clientPolicy.setReceiveTimeout(timeout);
				}
				timeout = (Long) this.requestContext.get(CONNECTION_TIMEOUT);
				if (timeout != null) {
					clientPolicy.setConnectionTimeout(timeout);
				}
				
				javax.ws.rs.core.Response response = this.client.invoke(httpMethod, payload);
				this.responseContext.put(WSConnection.STATUS_CODE, response.getStatus());
				this.responseContext.putAll(response.getMetadata());

				ArrayList contentTypes = (ArrayList)this.responseContext.get("content-type"); //$NON-NLS-1$
				String contentType = contentTypes != null ? (String)contentTypes.get(0):"application/octet-stream"; //$NON-NLS-1$
				return new HttpDataSource(url, (InputStream)response.getEntity(), contentType);
			} catch (IOException e) {
				throw new WebServiceException(e);
			}
		}

		@Override
		public Map<String, Object> getRequestContext() {
			return this.requestContext;
		}

		@Override
		public Map<String, Object> getResponseContext() {
			return this.responseContext;
		}

		@Override
		public Binding getBinding() {
			throw new UnsupportedOperationException();
		}

		@Override
		public EndpointReference getEndpointReference() {
			throw new UnsupportedOperationException();
		}

		@Override
		public <T extends EndpointReference> T getEndpointReference(Class<T> clazz) {
			throw new UnsupportedOperationException();
		}

		@Override
		public Response<DataSource> invokeAsync(DataSource msg) {
			throw new Unsu