package org.jboss.resteasy.test.profiling;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.ws.rs.core.Form;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;

import org.jboss.resteasy.client.jaxrs.ClientHttpEngine;
import org.jboss.resteasy.client.jaxrs.internal.ClientConfiguration;
import org.jboss.resteasy.client.jaxrs.internal.ClientInvocation;
import org.jboss.resteasy.client.jaxrs.internal.ClientResponse;
import org.jboss.resteasy.core.Dispatcher;
import org.jboss.resteasy.core.SynchronousDispatcher;
import org.jboss.resteasy.core.SynchronousExecutionContext;
import org.jboss.resteasy.mock.MockHttpRequest;
import org.jboss.resteasy.mock.MockHttpResponse;
import org.jboss.resteasy.specimpl.MultivaluedMapImpl;
import org.jboss.resteasy.spi.Registry;
import org.jboss.resteasy.spi.ResteasyProviderFactory;

/**
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @author <a href="mailto:rsigal@redhat.com">Ron Sigal</a>
 * @version $Revision: 1 $
 */
public class InMemoryClientEngine implements ClientHttpEngine
{
   protected Dispatcher dispatcher;
   protected URI baseUri;

   public InMemoryClientEngine()
   {
      dispatcher = new SynchronousDispatcher(ResteasyProviderFactory.getInstance());
   }

   public InMemoryClientEngine(final Dispatcher dispatcher)
   {
      this.dispatcher = dispatcher;
   }


   public URI getBaseUri()
   {
      return baseUri;
   }

   public void setBaseUri(URI baseUri)
   {
      this.baseUri = baseUri;
   }

   @Override
   public ClientResponse invoke(ClientInvocation request)
   {
      MockHttpRequest mockHttpRequest = MockHttpRequest.create(request.getMethod(), request.getUri(), baseUri);
      final MockHttpResponse mockResponse = new MockHttpResponse();
      mockHttpRequest.setAsynchronousContext(new SynchronousExecutionContext((SynchronousDispatcher)dispatcher, mockHttpRequest, mockResponse));
      loadHttpMethod(request, mockHttpRequest);
      dispatcher.invoke(mockHttpRequest, mockResponse);
      return createResponse(request, mockResponse);
   }

   protected ClientResponse createResponse(ClientInvocation request, final MockHttpResponse mockResponse)
   {
      InputStream is = new ByteArrayInputStream(mockResponse.getOutput());
      ClientResponse response = new InMemoryClientResponse(request.getClientConfiguration(), is);
      response.setStatus(mockResponse.getStatus());
      setHeaders(mockResponse, response);
      return response;
   }

   protected void setHeaders(final MockHttpResponse mockResponse, ClientResponse response)
   {
      MultivaluedMapImpl<String, String> responseHeaders = new MultivaluedMapImpl<String, String>();
      for (Entry<String, List<Object>> entry : mockResponse.getOutputHeaders().entrySet())
      {
         List<String> values = new ArrayList<String>(entry.getValue().size());
         for (Object value : entry.getValue())
         {
            values.add(value.toString());
         }
         responseHeaders.addMultiple(entry.getKey(), values);
      }
      response.setHeaders(responseHeaders);
   }

   public void loadHttpMethod(ClientInvocation request, MockHttpRequest mockHttpRequest)// throws Exception
   {
      // TODO: punt on redirects, for now.
      // if (httpMethod instanceof GetMethod && request.followRedirects())
      // httpMethod.setFollowRedirects(true);
      // else httpMethod.setFollowRedirects(false);

//      if (request.getEntity() != null && !request.getFormParameters().isEmpty())
//         throw new RuntimeException(Messages.MESSAGES.cannotSendFormParametersAndEntity());

      if (request.getEntity() instanceof Form)
      {
         commitHeaders(request, mockHttpRequest);
         Form form = (Form) request.getEntity();
         MultivaluedMap<String, String> map = form.asMap();
         for (Map.Entry<String, List<String>> formParam : map.entrySet())
         {
            String key = formParam.getKey();
            for (String value : formParam.getValue())
            {
               mockHttpRequest.getFormParameters().add(key, value);
            }
         }
      }
      else if (request.getEntity() != null)
      {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();

         MediaType bodyContentType = request.getHeaders().getMediaType();
         request.getHeaders().asMap().add(HttpHeaders.CONTENT_TYPE, bodyContentType.toString());

         try
         {
            request.writeRequestBody(baos);
         }
         catch (IOException e)
         {
            throw new RuntimeException(e);
         }
         // commit headers after byte array is complete.
         commitHeaders(request, mockHttpRequest);
         mockHttpRequest.content(baos.toByteArray());
         mockHttpRequest.contentType(bodyContentType);
      }
      else
      {
         commitHeaders(request, mockHttpRequest);
      }
   }

   public void commitHeaders(ClientInvocation request, MockHttpRequest mockHttpRequest)
   {
      MultivaluedMap<String, String> headers = mockHttpRequest.getMutableHeaders();
      headers.putAll(request.getHeaders().asMap());
   }

   public Registry getRegistry()
   {
      return this.dispatcher.getRegistry();
   }

   public Dispatcher getDispatcher()
   {
      return dispatcher;
   }

   public void close()
   {
      // empty
   }

   @Override
   public SSLContext getSslContext()
   {
      // TODO Auto-generated method stub
      return null;
   }

   @Override
   public HostnameVerifier getHostnameVerifier()
   {
      // TODO Auto-generated method stub
      return null;
   }

   public static class InMemoryClientResponse extends ClientResponse
   {
      private InputStream stream;

      protected InMemoryClientResponse(final ClientConfiguration configuration, final InputStream is)
      {
         super(configuration);
         stream = is;
      }

      @Override
      protected void setInputStream(InputStream is)
      {
         stream = is;
      }

      public InputStream getInputStream()
      {
         return stream;
      }

      @Override
      public void releaseConnection() throws IOException
      {
      }

      @Override
      public void releaseConnection(boolean consumeInputStream) throws IOException
      {
      }

   }

}
