/*
 * JBoss, the OpenSource J2EE webOS
 *
 * Distributable under LGPL license.
 * See terms of license at gnu.org.
 */
package org.jboss.remoting.transport.http;

import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.PostMethod;
import org.jboss.logging.Logger;
import org.jboss.remoting.CannotConnectException;
import org.jboss.remoting.ConnectionFailedException;
import org.jboss.remoting.InvokerLocator;
import org.jboss.remoting.RemoteClientInvoker;
import org.jboss.remoting.marshal.Marshaller;
import org.jboss.remoting.marshal.UnMarshaller;
import org.jboss.remoting.marshal.http.HTTPMarshaller;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

/**
 * @author <a href="mailto:tom@jboss.org">Tom Elrod</a>
 */
public class HTTPClientInvoker extends RemoteClientInvoker
{

   protected final Logger log = Logger.getLogger(getClass());

   public HTTPClientInvoker(InvokerLocator locator)
   {
      super(locator);
   }

   /**
    *
    * @param sessionId
    * @param invocation
    * @param marshaller
    * @return
    * @throws java.io.IOException
    * @throws org.jboss.remoting.ConnectionFailedException
    *
    */
   protected Object transport(String sessionId, Object invocation, Map metadata,
                              Marshaller marshaller, UnMarshaller unmarshaller)
         throws IOException, ConnectionFailedException
   {
      /**
      * //TODO: -TME Need to fully javadoc.
      * //TODO: -TME Need to think more on what the signature should be for this.  Have to pass the marshaller
      * to the actual transport implementation as will be up to the transport to figure out how
      * it should get the data from the marshaller (streamed or one chunck).  Am passing the invocation
      * at this point, because don't know if will need to do anything with it later on down the call stack
      * and currently, the marshaller does not have a reference to it (so HAVE to pass to marshaller at
      * some point).  Could change to pass invocation to MarshalFactory when getting Marshaller and
      * add it to Marshaller constructor, but then not part of it's interface.
      */
      String targetURL = getLocator().getOriginalURI();
      //useApacheHttpClient(targetURL);
      //useInnovationHttpClient(targetURL);
      Object httpResponse = useHttpURLConnection(targetURL, invocation, metadata, marshaller, unmarshaller);
//      Object httpResponse = useApacheHttpClient(targetURL, invocation, marshaller);

      return httpResponse;
   }

   private Object useHttpURLConnection(String url, Object invocation, Map metadata,
                                       Marshaller marshaller, UnMarshaller unmarshaller)
   {
      Object result = null;
      try
      {
         URL externalURL = new URL(url);
         HttpURLConnection conn = (HttpURLConnection) externalURL.openConnection();

         // Get the request method type
         boolean isPost = true;
         if(metadata != null)
         {
            String type = (String) metadata.get("TYPE");
            if(type != null && type.equals("GET"))
            {
               isPost = false;
            }

            // Set request headers
            Map header = (Map) metadata.get("HEADER");
            if(header != null)
            {
               Set keys = header.keySet();
               Iterator itr = keys.iterator();
               while(itr.hasNext())
               {
                  String key = (String) itr.next();
                  String value = (String) header.get(key);
                  log.debug("Setting request header with " + key + " : " + value);
                  conn.setRequestProperty(key, value);
               }
            }
         }

         if(isPost)
         {
            //POST
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setRequestMethod("POST");
            //conn.setRequestProperty("Content-type", "application/soap+xml");

            // TODO: -TME Does this really need to be set?
            //conn.setRequestProperty("Content-length", String.valueOf(((String)invocation).getBytes().length));

            //TODO: -TME How to set the SOAPAction header?
            if(metadata != null)
            {
               String soapAction = (String)metadata.get("SOAPAction");
               if (soapAction != null)
               {
                  conn.setRequestProperty("SOAPAction", soapAction);
               }
            }
            OutputStream stream = conn.getOutputStream();

            marshaller.write(invocation, stream);

            InputStream is = conn.getInputStream();

            Map headers = conn.getHeaderFields();

            result = unmarshaller.read(is, headers);

         }
         else
         {
            throw new Exception("HTTP GET opperation not currently supported.");
         }
      }
      catch(Exception e)
      {
         log.debug("Error invoking http client invoker.", e);
         throw new CannotConnectException("Can not connect http client invoker.", e);
      }

      return result;
   }

/*
   private void useInnovationHttpClient(String uri)
   {
      HTTPConnection conn = new HTTPConnection(uri);
      HttpOutputStream output = new HttpOutputStream();
      try
      {
         HTTPResponse response = conn.Post("", output);
         byte[] bytes = new String("foobar").getBytes();
         output.write(bytes);
         output.flush();
         System.out.println("response = " + response);

      }
      catch (IOException e)
      {
         e.printStackTrace();  //TODO: -TME Implement
      }
      catch (ModuleException e)
      {
         e.printStackTrace();  //TODO: -TME Implement
      }


   }
*/

   private Object useApacheHttpClient(String url, Object invocation, Marshaller marshaller)
         throws IOException
   {

      PostMethod post = new PostMethod(url);
      post.setRequestHeader("Content-type", "application/soap+xml");
      String body = (String) invocation;
      post.setRequestContentLength(body.length());
      post.setRequestBody(body);

      HttpClient client = new HttpClient();
      int status = client.executeMethod(post);
      //System.out.println("POST returned status " + status);
      return post.getResponseBodyAsString();
   }


   /**
    * subclasses must implement this method to provide a hook to connect to the remote server, if this applies
    * to the specific transport. However, in some transport implementations, this may not make must difference since
    * the connection is not persistent among invocations, such as SOAP.  In these cases, the method should
    * silently return without any processing.
    *
    * @throws org.jboss.remoting.ConnectionFailedException
    *
    */
   protected void handleConnect() throws ConnectionFailedException
   {
      // NO OP as not statefull connection
   }

   /**
    * subclasses must implement this method to provide a hook to disconnect from the remote server, if this applies
    * to the specific transport. However, in some transport implementations, this may not make must difference since
    * the connection is not persistent among invocations, such as SOAP.  In these cases, the method should
    * silently return without any processing.
    */
   protected void handleDisconnect()
   {
      // NO OP as not statefull connection
   }

   /**
    * Each implementation of the remote client invoker should have
    * a default data type that is uses in the case it is not specified
    * in the invoker locator uri.
    *
    * @return
    */
   protected String getDefaultDataType()
   {
      return HTTPMarshaller.DATATYPE;
   }

}