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.util.CaseInsensitiveMap;
import org.jboss.resteasy.util.CommitHeaderOutputStream;

import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.util.List;
import java.util.Map.Entry;

import static org.jboss.resteasy.util.HttpHeaderNames.*;

public class URLConnectionClientExecutor implements ClientExecutor
{

   public ClientResponse<?> execute(ClientRequest request) throws Exception
   {
      HttpURLConnection connection = createConnection(request);
      return execute(request, connection);
   }

   private void commitHeaders(ClientRequest request, HttpURLConnection connection)
   {
      for (Entry<String, List<String>> entry : request.getHeaders().entrySet())
      {
         String value = null;
         if (entry.getValue().size() == 1)
            value = entry.getValue().get(0);
         else
         {
            StringBuilder b = new StringBuilder();
            String add = "";
            for (String v : entry.getValue())
            {
               b.append(add).append(v);
               add = ",";
            }
            value = b.toString();
         }
         connection.addRequestProperty(entry.getKey(), value);
      }
   }

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

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

   protected HttpURLConnection createConnection(ClientRequest request) throws Exception
   {
      String uri = request.getUri();
      String httpMethod = request.getHttpMethod();

      HttpURLConnection connection = (HttpURLConnection) new URL(uri).openConnection();
      connection.setRequestMethod(httpMethod);
      return connection;
   }

   private <T> ClientResponse<T> execute(ClientRequest request, final HttpURLConnection connection) throws IOException
   {
      outputBody(request, connection);
      final int status = connection.getResponseCode();
      BaseClientResponse<T> response = new BaseClientResponse<T>(new BaseClientResponseStreamFactory()
      {
         public InputStream getInputStream() throws IOException
         {
            return (status < 300) ? connection.getInputStream() : connection.getErrorStream();
         }

         public void performReleaseConnection()
         {
            try
            {
               getInputStream().close();
            }
            catch (IOException e)
            {
            }
            connection.disconnect();
         }
      }, this);
      response.setProviderFactory(request.getProviderFactory());
      response.setStatus(status);
      response.setHeaders(getHeaders(connection));
      response.setAttributes(request.getAttributes());
      return response;
   }

   public void close()
   {
      // empty
   }

   private MultivaluedMap<String, String> getHeaders(
           final HttpURLConnection connection)
   {
      MultivaluedMap<String, String> headers = new CaseInsensitiveMap<String>();

      for (Entry<String, List<String>> header : connection.getHeaderFields()
              .entrySet())
      {
         if (header.getKey() != null)
            for (String value : header.getValue())
               headers.add(header.getKey(), value);
      }
      return headers;
   }

   private void outputBody(final ClientRequest request, final HttpURLConnection connection)
   {
      if (request.getBody() != null)
      {
         // System.out.println(request.getBody());
         if (connection.getRequestProperty(CONTENT_TYPE) == null)
         {
            String type = request.getBodyContentType().toString();
            connection.addRequestProperty(CONTENT_TYPE, type);
         }
         try
         {
            final CommitHeaderOutputStream commit = new CommitHeaderOutputStream();
            CommitHeaderOutputStream.CommitCallback callback = new CommitHeaderOutputStream.CommitCallback()
            {
               @Override
               public void commit()
               {
                  connection.setDoOutput(true);
                  commitHeaders(request, connection);
                  OutputStream os = null;
                  try
                  {
                     os = connection.getOutputStream();
                  }
                  catch (IOException e)
                  {
                     throw new RuntimeException(e);
                  }
                  commit.setDelegate(os);

               }
            };
            commit.setHeaders(callback);
            try
            {
               request.writeRequestBody(request.getHeadersAsObjects(), commit);
            }
            finally
            {
               commit.getDelegate().flush();
               commit.getDelegate().close();
            }
         }
         catch (IOException e)
         {
            throw new RuntimeException(e);
         }
      }
      else
      {
         commitHeaders(request, connection);
      }
   }
}
