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

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.core.Dispatcher;
import org.jboss.resteasy.core.SynchronousDispatcher;
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;

import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriBuilder;
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;

/**
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
@SuppressWarnings("unchecked")
public class InMemoryClientExecutor implements ClientExecutor
{
   protected Dispatcher dispatcher;
   protected URI baseUri;

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

   public InMemoryClientExecutor(Dispatcher dispatcher)
   {
      this.dispatcher = dispatcher;
   }

   public URI getBaseUri()
   {
      return baseUri;
   }

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

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

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


   public ClientResponse execute(ClientRequest request) throws Exception
   {
      MockHttpRequest mockHttpRequest = MockHttpRequest.create(request.getHttpMethod(), new URI(request.getUri()),
              baseUri);
      loadHttpMethod(request, mockHttpRequest);

      final MockHttpResponse mockResponse = new MockHttpResponse();
      dispatcher.invoke(mockHttpRequest, mockResponse);
      return createResponse(request, mockResponse);
   }

   protected BaseClientResponse createResponse(ClientRequest request, final MockHttpResponse mockResponse)
   {
      BaseClientResponseStreamFactory streamFactory = createStreamFactory(mockResponse);
      BaseClientResponse response = new BaseClientResponse(streamFactory, this);
      response.setStatus(mockResponse.getStatus());
      setHeaders(mockResponse, response);
      response.setProviderFactory(request.getProviderFactory());
      response.setAttributes(request.getAttributes());
      return response;
   }

   protected void setHeaders(final MockHttpResponse mockResponse, BaseClientResponse 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 static BaseClientResponseStreamFactory createStreamFactory(final MockHttpResponse mockResponse)
   {
      return new BaseClientResponseStreamFactory()
      {
         InputStream stream;

         public InputStream getInputStream() throws IOException
         {
            if (stream == null)
            {
               stream = new ByteArrayInputStream(mockResponse.getOutput());
            }
            return stream;
         }

         public void performReleaseConnection()
         {
         }
      };
   }

   public void loadHttpMethod(ClientRequest 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.getBody() != null && !request.getFormParameters().isEmpty())
         throw new RuntimeException("You cannot send both form parameters and an entity body");

      if (!request.getFormParameters().isEmpty())
      {
         commitHeaders(request, mockHttpRequest);
         for (Map.Entry<String, List<String>> formParam : request.getFormParameters().entrySet())
         {
            String key = formParam.getKey();
            for (String value : formParam.getValue())
            {
               mockHttpRequest.getFormParameters().add(key, value);
            }
         }
      }
      else if (request.getBody() != null)
      {
         ByteArrayOutputStream baos = new ByteArrayOutputStream();

         MediaType bodyContentType = request.getBodyContentType();
         request.getHeadersAsObjects().add(HttpHeaders.CONTENT_TYPE, bodyContentType.toString());

         request.writeRequestBody(request.getHeadersAsObjects(), baos);
         // commit headers after byte array is complete.
         commitHeaders(request, mockHttpRequest);
         mockHttpRequest.content(baos.toByteArray());
         mockHttpRequest.contentType(bodyContentType);
      }
      else
      {
         commitHeaders(request, mockHttpRequest);
      }
   }

   public void commitHeaders(ClientRequest request, MockHttpRequest mockHttpRequest)
   {
      MultivaluedMap headers = mockHttpRequest.getHttpHeaders().getRequestHeaders();
      headers.putAll(request.getHeaders());
   }

   private void setBody(ClientRequest request, MockHttpRequest mockHttpRequest) throws IOException
   {
      if (request.getBody() == null)
         return;

   }

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

   public Dispatcher getDispatcher()
   {
      return dispatcher;
   }

   public void close()
   {
      // empty
   }
   
}
