package org.jboss.resteasy.client.core.executors;

import org.apache.commons.httpclient.Header;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnectionManager;
import org.apache.commons.httpclient.HttpMethodBase;
import org.apache.commons.httpclient.methods.DeleteMethod;
import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.jboss.resteasy.client.ClientExecutor;
import org.jboss.resteasy.client.ClientRequest;
import org.jboss.resteasy.client.ClientResponse;
import org.jboss.resteasy.client.core.BaseClientResponse;
import org.jboss.resteasy.client.core.BaseClientResponse.BaseClientResponseStreamFactory;
import org.jboss.resteasy.client.core.SelfExpandingBufferredInputStream;
import org.jboss.resteasy.client.exception.mapper.ApacheHttpClient3ExceptionMapper;
import org.jboss.resteasy.client.exception.mapper.ClientExceptionMapper;
import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.util.CaseInsensitiveMap;
import org.jboss.resteasy.util.Types;

import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriBuilder;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

/**
 * This class will be removed in Resteasy 3.0.
 *
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
@Deprecated
public class ApacheHttpClientExecutor implements ClientExecutor
{
   protected HttpClient httpClient;
   protected boolean createdHttpClient;
   protected boolean closed;
   
   static synchronized private void checkClientExceptionMapper()
   {  
      if (ResteasyProviderFactory.getInstance().getClientExceptionMapper(Exception.class) == null)
      {
         Type exceptionType = Types.getActualTypeArgumentsOfAnInterface(ApacheHttpClient3ExceptionMapper.class, ClientExceptionMapper.class)[0];
         ResteasyProviderFactory.getInstance().addClientExceptionMapper(new ApacheHttpClient3ExceptionMapper(), exceptionType);
      }
   }
   
   public ApacheHttpClientExecutor()
   {
      this.httpClient = new HttpClient();
      createdHttpClient = true;
      checkClientExceptionMapper();
   }

   public ApacheHttpClientExecutor(HttpClient httpClient)
   {
      this.httpClient = httpClient;
      checkClientExceptionMapper();
   }

   public HttpClient getHttpClient()
   {
      return httpClient;
   }

   public static CaseInsensitiveMap<String> extractHeaders(
           HttpMethodBase baseMethod)
   {
      final CaseInsensitiveMap<String> headers = new CaseInsensitiveMap<String>();

      for (Header header : baseMethod.getResponseHeaders())
      {
         headers.add(header.getName(), header.getValue());
      }
      return headers;
   }

   public ClientRequest createRequest(String uriTemplate)
   {
      return new ClientRequest(uriTemplate, this);
   }

   public ClientRequest createRequest(UriBuilder uriBuilder)
   {
      return new ClientRequest(uriBuilder, this);
   }


   @SuppressWarnings("unchecked")
   public ClientResponse execute(ClientRequest request) throws Exception
   {
      String uri = request.getUri();
      final HttpMethodBase httpMethod = createHttpMethod(uri, request.getHttpMethod());
      loadHttpMethod(request, httpMethod);

      int status = httpClient.executeMethod(httpMethod);

      BaseClientResponse response = new BaseClientResponse(new BaseClientResponseStreamFactory()
      {
         InputStream stream;

         public InputStream getInputStream() throws IOException
         {
            if (stream == null)
            {
               stream = new SelfExpandingBufferredInputStream(httpMethod.getResponseBodyAsStream());
            }
            return stream;
         }

         public void performReleaseConnection()
         {
            try
            {
               httpMethod.releaseConnection();
            }
            catch (Exception ignored)
            {}
            try
            {
               stream.close();
            }
            catch (Exception ignored)
            {}
         }
      }, this);
      response.setAttributes(request.getAttributes());
      response.setStatus(status);
      response.setHeaders(extractHeaders(httpMethod));
      response.setProviderFactory(request.getProviderFactory());
      return response;
   }

   private HttpMethodBase createHttpMethod(String url, String restVerb)
   {
      if ("GET".equals(restVerb))
      {
         return new GetMethod(url);
      }
      else if ("POST".equals(restVerb))
      {
         return new PostMethod(url);
      }
      else if ("DELETE".equals(restVerb))
      {
         return new DeleteMethod(url);
      }
      else
      {
         final String verb = restVerb;
         return new PostMethod(url)
         {
            @Override
            public String getName()
            {
               return verb;
            }
         };
      }
   }

   private static class ClientRequestEntity implements RequestEntity
   {
      private byte[] bytes;
      private String contentType;

      private ClientRequestEntity(String contentType, byte[] bytes)
      {
         this.contentType = contentType;
         this.bytes = bytes;
      }

      public boolean isRepeatable()
      {
         return true;
      }

      public void writeRequest(OutputStream outputStream) throws IOException
      {
         //wrapper.sync();
         //writer.writeTo(target, type, genericType, annotations, mediaType, wrapper, outputStream);
         // i used to have it the above way, but I don't think you can set headers once you get into this method
         outputStream.write(bytes);
      }

      public long getContentLength()
      {
         return bytes.length;
      }

      public String getContentType()
      {
         return contentType;
      }
   }

   public void loadHttpMethod(ClientRequest request, HttpMethodBase httpMethod) throws Exception
   {
      if (httpMethod instanceof GetMethod && request.followRedirects()) httpMethod.setFollowRedirects(true);
      else httpMethod.setFollowRedirects(false);

      if (request.getBody() != null && !request.getFormParameters().isEmpty())
         throw new RuntimeException("You cannot send both form parameters and an entity body");

      if (!request.getFormParameters().isEmpty())
      {
         commitHeaders(request, httpMethod);
         PostMethod post = (PostMethod) httpMethod;

         for (Map.Entry<String, List<String>> formParam : request.getFormParameters().entrySet())
         {
            List<String> values = formParam.getValue();
            for (String value : values)
            {
               post.addParameter(formParam.getKey(), value);
            }
         }
      }
      else if (request.getBody() != null)
      {
         if (!(httpMethod instanceof EntityEnclosingMethod))
            throw new RuntimeException("A GET request cannot have a body.");
         ByteArrayOutputStream baos = new ByteArrayOutputStream();
         request.writeRequestBody(request.getHeadersAsObjects(), baos);
         commitHeaders(request, httpMethod);
         ClientRequestEntity requestEntity = new ClientRequestEntity(request.getBodyContentType().toString(), baos.toByteArray());
         EntityEnclosingMethod post = (EntityEnclosingMethod) httpMethod;
         post.setRequestEntity(requestEntity);
      }
      else
      {
         commitHeaders(request, httpMethod);
      }
   }

   public void commitHeaders(ClientRequest request, HttpMethodBase httpMethod)
   {
      MultivaluedMap<String, String> headers = request.getHeaders();
      for (Map.Entry<String, List<String>> header : headers.entrySet())
      {
         List<String> values = header.getValue();
         for (String value : values)
         {
//               System.out.println(String.format("setting %s = %s", header.getKey(), value));
            httpMethod.addRequestHeader(header.getKey(), value);
         }
      }
   }

   public boolean isClosed()
   {
      return closed;
   }
   
   public void close() throws Exception
   {
      if (closed)
      {
         return;
      }
      if (createdHttpClient)
      { 
         HttpConnectionManager manager = httpClient.getHttpConnectionManager();
         if (manager == null)
         {
            return;
         }
         Method shutdown = null;
         try
         {
            shutdown = manager.getClass().getDeclaredMethod("shutdown");
         }
         catch (Exception e)
         {
            // ignore
            return;
         }
         shutdown.invoke(manager);
         closed = true;
      }
   }
   
   public void finalize() throws Throwable
   {
      close();
      super.finalize();
   }
}
