package org.jboss.resteasy.springmvc;

import org.jboss.resteasy.core.ServerResponse;
import org.jboss.resteasy.core.SynchronousDispatcher;
import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.util.HttpHeaderNames;
import org.jboss.resteasy.util.MediaTypeHelper;
import org.springframework.validation.BindingResult;
import org.springframework.web.servlet.View;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * @author <a href="mailto:sduskis@gmail.com">Solomon Duskis</a>
 * @version $Revision: 1 $
 */

@SuppressWarnings("rawtypes")
public class ResteasyView implements View
{

   private MediaType contentType = null;
   private List<MediaType> potentialContentTypes = null;
   private SynchronousDispatcher dispatcher;

   public ResteasyView(String contentType, SynchronousDispatcher dispatcher)
   {
      setContentType(contentType);
      setDispatcher(dispatcher);
   }

   public ResteasyView()
   {
   }

   public SynchronousDispatcher getDispatcher()
   {
      return dispatcher;
   }

   public void setDispatcher(SynchronousDispatcher dispatcher)
   {
      this.dispatcher = dispatcher;
   }

   public String getContentType()
   {
      return contentType.toString();
   }

   public List<String> getPotentialContentTypes()
   {
      List<String> contentTypes = new ArrayList<String>(potentialContentTypes.size());
      for (MediaType mediaType : potentialContentTypes)
      {
         contentTypes.add(mediaType.toString());
      }
      return contentTypes;
   }

   public void setPotentialContentTypes(List<String> potentialContentTypes)
   {
      this.potentialContentTypes = new ArrayList<MediaType>();
      for (String type : potentialContentTypes)
      {
         this.potentialContentTypes.add(MediaType.valueOf(type));
      }
   }

   public void render(final Map model, final HttpServletRequest servletRequest,
                      final HttpServletResponse servletResponse) throws Exception
   {
      ResteasyWebHandlerTemplate template = new ResteasyWebHandlerTemplate<Void>(dispatcher.getProviderFactory())
      {
         protected Void handle(ResteasyRequestWrapper requestWrapper,
                               HttpResponse response) throws Exception
         {
            HttpRequest httpRequest = requestWrapper.getHttpRequest();
            dispatcher.pushContextObjects(httpRequest, response);
            try
            {
               try
               {
                  MediaType resolvedContentType = resolveContentType(httpRequest,
                          httpRequest.getHttpHeaders().getMediaType());
                  ServerResponse responseInvoker = getResponse(model, resolvedContentType);
                  if (responseInvoker != null)
                  {
                     responseInvoker.writeTo(httpRequest, response, dispatcher.getProviderFactory());
                  }
               }
               catch (Exception e)
               {
                  dispatcher.handleWriteResponseException(httpRequest, response, e);
               }
               return null;
            }
            finally
            {
               dispatcher.clearContextData();
            }
         }
      };
      ResteasyRequestWrapper requestWrapper = RequestUtil.getRequestWrapper(servletRequest);
      template.handle(requestWrapper, servletResponse);
   }

   private MediaType resolveContentType(HttpRequest jaxrsRequest, MediaType mt)
   {
      if (mt == null || MediaType.MEDIA_TYPE_WILDCARD.equals(mt.getType()))
      {
         List<MediaType> acceptableMediaTypes = jaxrsRequest.getHttpHeaders()
                 .getAcceptableMediaTypes();

         if (contentType != null && isAcceptable(acceptableMediaTypes, contentType))
         {
            return contentType;
         }

         return MediaTypeHelper.getBestMatch(potentialContentTypes, acceptableMediaTypes);
      }
      return mt;
   }

   private boolean isAcceptable(List<MediaType> acceptableMediaTypes,
                                MediaType potentialContentType)
   {
      boolean isAcceptable = false;
      for (MediaType acceptableMediaType : acceptableMediaTypes)
      {
         if (acceptableMediaType.isCompatible(potentialContentType))
         {
            isAcceptable = true;
            break;
         }
      }
      return isAcceptable;
   }

   protected ServerResponse getResponse(Map model, MediaType mt)
   {
      Collection modelValues = model.values();
      for (Object value : modelValues)
      {
         if (value instanceof ServerResponse)
         {
            return (ServerResponse) value;
         }
      }

      if (model.size() == 1)
      {
         return createResponse(modelValues.iterator().next(), mt);
      }
      if (model.size() == 2)
      {
         for (Object value : modelValues)
         {
            if (!(value instanceof BindingResult))
            {
               return createResponse(value, mt);
            }
         }
      }
      return null;
   }

   private ServerResponse createResponse(Object value, MediaType contentType)
   {
      ServerResponse responseImpl = new ServerResponse();
      responseImpl.setEntity(value);
      if (contentType != null)
         responseImpl.getMetadata().putSingle(HttpHeaderNames.CONTENT_TYPE, contentType);
      return responseImpl;
   }

   public void setContentType(String contentType)
   {
      this.contentType = MediaType.valueOf(contentType);
   }

}
