package org.apache.soap.transport.http;

import HTTPClient.HTTPConnection;
import HTTPClient.HTTPResponse;
import HTTPClient.ModuleException;
import HTTPClient.NVPair;
import org.apache.soap.Constants;
import org.apache.soap.Envelope;
import org.apache.soap.SOAPException;
import org.apache.soap.encoding.SOAPMappingRegistry;
import org.apache.soap.rpc.SOAPContext;
import org.apache.soap.transport.SOAPTransport;
import org.apache.soap.transport.TransportMessage;

import javax.mail.MessagingException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;

public class SOAPHTTPClientTransport implements SOAPTransport {
    private BufferedReader responseReader;
    private Hashtable responseHeaders;
    private SOAPContext responseSOAPContext;
    private int timeout = 0;
    private HTTPConnection conn;

    /**
     * Set the HTTP read timeout.
     *
     * @param timeout the amount of time, in ms, to block on reading data.
     *                A zero value indicates an infinite timeout.
     */
    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }

    /**
     * Get the HTTP read timeout.
     *
     * @return the amount of time, in ms, to block on reading data.
     */
    public int getTimeout() {
        return timeout;
    }

    /**
     * This method is used to request that an envelope be posted to the
     * given URL. The response (if any) must be gotten by calling the
     * receive() function.
     *
     * @param sendTo the URL to send the envelope to
     * @param action the SOAPAction header field value
     * @param headers any other header fields to go to as protocol headers
     * @param env the envelope to send
     * @param smr the XML<->Java type mapping registry (passed on)
     * @param ctx the request SOAPContext
     *
     * @exception SOAPException with appropriate reason code if problem
     */
    public void send(URL sendTo, String action, Hashtable headers,
                     Envelope env, SOAPMappingRegistry smr, SOAPContext ctx)
            throws SOAPException {
        try {
            // Reuse the old connection if appropriate, otherwise reuse the existing one
            if (conn == null || !(conn.getHost().equals(sendTo.getHost())
                    && conn.getPort() == sendTo.getPort()
                    && conn.getProtocol().equals(sendTo.getProtocol()))) {
                conn = new HTTPConnection(sendTo);
            }
            conn.setTimeout(timeout);
            String payload = null;
            if (env != null) {
                StringWriter payloadSW = new StringWriter();
                env.marshall(payloadSW, smr, ctx);
                payload = payloadSW.toString();
            }
            TransportMessage responseMessage;
            try {
                TransportMessage requestMessage = new TransportMessage(payload, ctx, headers);
                requestMessage.save();

                // Prepare the request headers
                NVPair[] postHeaders = new NVPair[2 + ((headers == null)?0:headers.size())];
                postHeaders[0] = new NVPair(Constants.HEADER_SOAP_ACTION, (action != null) ? ('\"' + action + '\"') : "");
                postHeaders[1] = new NVPair(Constants.HEADER_CONTENT_TYPE, requestMessage.getContentType());
                if (headers != null) {
                    int headerOffset = 2;
                    for (Enumeration e = headers.keys(); e.hasMoreElements(); headerOffset++) {
                        Object key = e.nextElement();
                        postHeaders[headerOffset] = new NVPair((String) key, (String) headers.get(key));
                    }
                }
                byte[] content = requestMessage.getBytes();

                // Perform the actual posting
                HTTPResponse response = conn.Post(sendTo.getFile(), content, postHeaders);
                responseHeaders = new Hashtable();
                for (Enumeration enum = response.listHeaders(); enum.hasMoreElements();) {
                    String key = (String) enum.nextElement();
                    responseHeaders.put(key, response.getHeader(key));
                }
                SOAPContext responseCtx = new SOAPContext();
                responseMessage = new TransportMessage(response.getInputStream(),
                        response.getHeaderAsInt(Constants.HEADER_CONTENT_LENGTH),
                        response.getHeader(Constants.HEADER_CONTENT_TYPE),
                        responseCtx,
                        responseHeaders);

                // Extract envelope and SOAPContext
                responseMessage.read();
            } catch (MessagingException me) {
                throw new IOException("Failed to encode mime multipart: " + me);
            } catch (UnsupportedEncodingException uee) {
                throw new IOException("Failed to encode mime multipart: " + uee);
            } catch (ModuleException me) {
                throw new IOException("HTTPClient Module failed: " + me);
            }
            Reader envReader = responseMessage.getEnvelopeReader();
            if (envReader != null)
                responseReader = new BufferedReader(envReader);
            else
                responseReader = null;
            responseSOAPContext = responseMessage.getSOAPContext();
            responseHeaders = responseMessage.getHeaders();
        } catch (IllegalArgumentException e) {
            throw new SOAPException(Constants.FAULT_CODE_CLIENT, e.getMessage(), e);
        } catch (MessagingException e) {
            throw new SOAPException(Constants.FAULT_CODE_CLIENT, e.getMessage(), e);
        } catch (IOException e) {
            throw new SOAPException(Constants.FAULT_CODE_CLIENT, e.getMessage(), e);
        }
    }

    /**
     * Return a buffered reader to receive back the response to whatever
     * was sent to whatever.
     *
     * @return a reader to read the results from or null if that's not
     *         possible.
     */
    public BufferedReader receive() {
        return responseReader;
    }

    /**
     * Return access to headers generated by the protocol.
     *
     * @return a hashtable containing all the headers
     */
    public Hashtable getHeaders() {
        return responseHeaders;
    }

    /**
     * Return the SOAPContext associated with the response.
     *
     * @return response SOAPContext
     */
    public SOAPContext getResponseSOAPContext() {
        return responseSOAPContext;
    }
}