/*
 * Copyright 1999-2010 University of Chicago
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
 * compliance with the License.  You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under the License is
 * distributed on an "AS IS" BASIS,WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
 * express or implied.
 *
 * See the License for the specific language governing permissions and limitations under the License.
 */
package org.globus.net;

import java.net.Socket;
import java.net.URL;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ProtocolException;

import org.globus.common.ChainedIOException;
import org.globus.util.http.HTTPProtocol;
import org.globus.util.http.HTTPResponseParser;
import org.globus.util.http.HTTPChunkedInputStream;
import org.globus.util.http.HTTPChunkedOutputStream;
import org.globus.gsi.GSIConstants;
import org.globus.gsi.gssapi.GSSConstants;
import org.globus.gsi.gssapi.net.GssSocket;
import org.globus.gsi.gssapi.net.GssSocketFactory;

import org.gridforum.jgss.ExtendedGSSManager;
import org.gridforum.jgss.ExtendedGSSContext;

import org.ietf.jgss.GSSManager;
import org.ietf.jgss.GSSException;
import org.ietf.jgss.GSSContext;

public class GSIHttpURLConnection extends GSIURLConnection {

    public static final int PORT = 8443;

    private static final String USER_AGENT =
        "Java-Globus-GASS-HTTP/1.1.0";
    private static final String POST_CONTENT_TYPE =
        "application/x-www-form-urlencoded";

    private Socket socket;
    private int port;

    private HTTPResponseParser response;
    private InputStream is;
    private OutputStream os;

    public GSIHttpURLConnection(URL u) {
        super(u);
    }

    public synchronized void connect()
        throws IOException {

        if (this.connected) {
            return;
        } else {
            this.connected = true;
        }

        this.port = (url.getPort() == -1) ? PORT : url.getPort();

        GSSManager manager = ExtendedGSSManager.getInstance();

        ExtendedGSSContext context = null;

        try {
            context =
                (ExtendedGSSContext)manager.createContext(getExpectedName(),
                                                          GSSConstants.MECH_OID,
                                                          this.credentials,
                                                          GSSContext.DEFAULT_LIFETIME);

            switch (this.delegationType) {

            case GSIConstants.DELEGATION_NONE:
                context.requestCredDeleg(false);
                break;
            case GSIConstants.DELEGATION_LIMITED:
                context.requestCredDeleg(true);
                context.setOption(GSSConstants.DELEGATION_TYPE,
                                  GSIConstants.DELEGATION_TYPE_LIMITED);
                break;
            case GSIConstants.DELEGATION_FULL:
                context.requestCredDeleg(true);
                context.setOption(GSSConstants.DELEGATION_TYPE,
                                  GSIConstants.DELEGATION_TYPE_FULL);
                break;
            default:
                context.requestCredDeleg(true);
                context.setOption(GSSConstants.DELEGATION_TYPE,
                                  new Integer(this.delegationType));

            }

            if (this.gssMode != null) {
                context.setOption(GSSConstants.GSS_MODE,
                                  gssMode);
            }

        } catch (GSSException e) {
            throw new ChainedIOException("Failed to init GSI context", e);
        }

        GssSocketFactory factory = GssSocketFactory.getDefault();

        socket = factory.createSocket(url.getHost(), this.port, context);

        ((GssSocket)socket).setAuthorization(authorization);
    }

    public synchronized void disconnect() {
        if (socket != null) {
            try { socket.close(); } catch (Exception e) {}
            socket = null;
        }
    }

    public synchronized OutputStream getOutputStream()
        throws IOException {
        // maybe already doing a GET, so only input stream
        if (this.is != null && this.os == null) {
            throw new ProtocolException(
                           "Cannot write output after reading input");
        }
        if (this.os == null) {
            connect();
            String header =
                HTTPProtocol.createPUTHeader(url.getFile(),
                                             url.getHost() + ":" + port,
                                             USER_AGENT,
                                             POST_CONTENT_TYPE, -1, true);
            OutputStream wrapped = socket.getOutputStream();
            wrapped.write(header.getBytes());
            // create an output stream that will stream
            // the result using chunked coding
            this.os = new HTTPChunkedOutputStream(wrapped);
        }

        return os;
    }

    public synchronized InputStream getInputStream()
        throws IOException {

        if (this.is == null) {
            connect();

            if (this.os == null) {
                // if no output stream already created, send a GET request
                OutputStream out = socket.getOutputStream();

                String msg =
                    HTTPProtocol.createGETHeader(url.getFile(),
                                                 url.getHost() + ":" + this.port,
                                                 USER_AGENT);

                out.write( msg.getBytes() );
                out.flush();
            } else {
                // if getOutputStream() was called previously, flush it
                this.os.flush();
                this.os.close();
                this.os = null;
            }

            InputStream in = socket.getInputStream();

            response = new HTTPResponseParser(in);

            if (!response.isOK()) {
                throw new IOException(response.getMessage());
            }

            if (response.isChunked()) {
                is = new HTTPChunkedInputStream(in);
            } else {
                is = in;
            }
        }

        return is;
    }

    public String getHeaderField(String name) {
        if (response == null) {
            return null;
        }

        if (name.equalsIgnoreCase("content-type")) {
            return response.getContentType();
        } else if (name.equalsIgnoreCase("content-length")) {
            return String.valueOf(response.getContentLength());
        } else {
            return null;
        }
    }

}
