package org.jboss.resteasy.test.nextgen.resource;

import org.jboss.resteasy.test.BaseResourceTest;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;

import javax.annotation.Priority;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.EntityTag;
import javax.ws.rs.core.Link;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.NewCookie;
import javax.ws.rs.core.PathSegment;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import static org.jboss.resteasy.test.TestPortProvider.generateURL;

/**
 * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
 * @version $Revision: 1 $
 */
public class Response3Test extends BaseResourceTest
{
   public abstract static class TemplateFilter implements ContainerResponseFilter {

      public static final String OPERATION = "OPERATION";
      public static final String PROPERTYNAME = "getSetProperty";
      public static final String HEADER = "HEADER";

      protected ContainerRequestContext requestContext;
      protected ContainerResponseContext responseContext;

      @Override
      public void filter(ContainerRequestContext requestContext,
                         ContainerResponseContext responseContext) throws IOException {
         this.requestContext = requestContext;
         this.responseContext = responseContext;
         String operation = getHeaderString();
         Method[] methods = getClass().getMethods();
         for (Method method : methods)
            if (operation.equalsIgnoreCase(method.getName())) {
               try {
                  method.invoke(this);
                  return;
               } catch (Exception e) {
                  e.printStackTrace();
                  responseContext.setStatus(Response.Status.SERVICE_UNAVAILABLE
                          .getStatusCode());
                  setEntity(e.getMessage());
                  return;
               }
            }
         operationMethodNotFound(operation);
      }

      protected void operationMethodNotFound(String operation) {
         responseContext.setStatus(Response.Status.SERVICE_UNAVAILABLE.getStatusCode());
         setEntity("Operation " + operation + " not implemented");
      }

      // ////////////////////////////////////////////////////////////////////
      protected static <T> String collectionToString(Collection<T> collection) {
         StringBuilder sb = new StringBuilder();
         for (T item : collection) {
            String replace = item.toString().toLowerCase().replace("_", "-")
                    .replace(" ", "");
            sb.append(replace).append(" ");
         }
         return sb.toString();
      }

      protected boolean assertTrue(boolean conditionTrue, Object... msg) {
         if (conditionTrue)
            return false;
         StringBuilder sb = new StringBuilder();
         if (msg != null)
            for (Object str : msg)
               sb.append(str).append(" ");
         setEntity(sb.toString());
         responseContext.setStatus(Response.Status.NOT_ACCEPTABLE.getStatusCode());
         return true;
      }

      // might be replaced with ctx.getStringHeader()
      protected String getHeaderString() {
         MultivaluedMap<String, Object> headers = responseContext.getHeaders();
         return (String) headers.getFirst(OPERATION);
      }

      protected void setEntity(String entity) {
         responseContext.setEntity(entity, null, MediaType.TEXT_PLAIN_TYPE);
      }

   }

   @Provider
   @Priority(500)
// reverse order
   public static class ResponseFilter extends TemplateFilter {
      public static final String COOKIENAME = "CookieName";
      public static final String ENTITY = "ResponseFilterEntity";
      public static final String NULL = "NULL";
      public static final String RELATION = "relation";

      public void getAllowedMethods() {
         Set<String> set = responseContext.getAllowedMethods();
         setEntity(collectionToString(set));
         for (String s : set)
            if (!s.toUpperCase().equals(s)) {
               setEntity(s + " is not uppercase");
               break;
            }
      }

      public void getCookies() {
         Map<String, NewCookie> cookies = responseContext.getCookies();
         setEntity(collectionToString(cookies.keySet()));
      }

      public void getCookiesIsReadOnly() {
         NewCookie cookie = new NewCookie(COOKIENAME, COOKIENAME);
         Map<String, NewCookie> cookies = responseContext.getCookies();
         if (assertTrue(!cookies.containsKey(COOKIENAME), COOKIENAME,
                 "is already present"))
            return;
         try {
            cookies.put(COOKIENAME, cookie);
         } catch (Exception e) {
            // Not mandatory, but possible as cookies is read-only
         }
         cookies = responseContext.getCookies();
         if (assertTrue(!cookies.containsKey(COOKIENAME),
                 "#getCookies is Not readOnly"))
            return;
         setEntity("getCookies is read-only as expected");
      }

      public void getDate() {
         Date date = responseContext.getDate();
         long milis = date == null ? 0 : date.getTime();
         setEntity(String.valueOf(milis));
      }

      public void getEntity() {
         byte[] entity = (byte[]) responseContext.getEntity();
         if (entity != null)
            setEntity(new String(entity) + new String(entity));
         else
            setEntity(NULL);
      }

      public void getEntityAnnotations() {
         Annotation[] annotations = responseContext.getEntityAnnotations();
         if (annotations != null && annotations.length != 0) {
            String[] names = new String[annotations.length];
            for (int i = 0; i != annotations.length; i++)
               names[i] = annotations[i].annotationType().getName();
            setEntity(collectionToString(Arrays.asList(names)));
         } else
            setEntity(NULL);
      }

      public void getEntityClass() {
         Class<?> clazz = responseContext.getEntityClass();
         setEntity(clazz.getName());
      }

      public void getEntityStream() throws IOException {
         OutputStream stream = responseContext.getEntityStream();
         if (stream == null)
            setEntity(NULL);
         else
            setEntity(ENTITY);
      }

      public void getEntityTag() {
         EntityTag tag = responseContext.getEntityTag();
         setEntity(tag == null ? NULL : tag.getValue());
      }

      public void getEntityType() {
         Type type = responseContext.getEntityType();
         String name = NULL;
         if (type instanceof Class)
            name = ((Class<?>) type).getName();
         else if (type != null)
            name = type.getClass().getName();
         setEntity(name);
      }

      public void getHeaders() {
         MultivaluedMap<String, Object> headers = responseContext.getHeaders();
         setEntity(collectionToString(headers.keySet()));
      }

      public void getHeadersIsMutable() {
         MultivaluedMap<String, Object> headers = responseContext.getHeaders();
         if (assertTrue(!headers.containsKey(HEADER), HEADER,
                 "header is alredy in header map"))
            return;
         headers.add(HEADER, HEADER);
         headers = responseContext.getHeaders();
         if (assertTrue(headers.containsKey(HEADER), HEADER,
                 "header is not in header map"))
            return;
         // second filter run
      }

      public void getHeaderStringOperation() {
         String header = responseContext.getHeaderString(OPERATION);
         setEntity(header);
      }

      public void getHeaderStringHeader() {
         String header = responseContext.getHeaderString(HEADER);
         setEntity(header == null ? NULL : header);
      }

      public void getLanguage() {
         Locale locale = responseContext.getLanguage();
         setEntity(locale == null ? NULL : locale.toString());
      }

      public void getLastModified() {
         Date date = responseContext.getLastModified();
         setEntity(date == null ? NULL : String.valueOf(date.getTime()));
      }

      public void getLength() {
         int len = responseContext.getLength();
         setEntity(String.valueOf(len));
      }

      public void getLink() {
         Link link = responseContext.getLink(RELATION);
         setLinkForGetLink(link);
      }

      public void getLinkBuilder() {
         Link.Builder builder = responseContext.getLinkBuilder(RELATION);
         if (builder != null) {
            Link link = builder.build();
            setLinkForGetLink(link);
         } else
            setEntity(NULL);
      }

      private void setLinkForGetLink(Link link) {
         String entity = NULL;
         if (link != null && link.getUri() != null)
            entity = link.getUri().toASCIIString();
         setEntity(entity);
      }

      public void getLocation() {
         URI uri = responseContext.getLocation();
         setEntity(uri == null ? NULL : uri.toASCIIString());
      }

      public void getMediaType() {
         MediaType type = responseContext.getMediaType();
         setEntity(type == null ? NULL : type.toString());
      }

      public void getStatus() {
         int status = responseContext.getStatus();
         responseContext.setStatus(Response.Status.OK.getStatusCode());
         setEntity(String.valueOf(status));
      }

      public void getStatusNotSet() {
         getStatus();
      }

      public void getStatusInfo() {
         Response.StatusType type = responseContext.getStatusInfo();
         if (type == null) {
            setEntity(NULL);
            responseContext.setStatus(Response.Status.OK.getStatusCode());
            return;
         }
         int status = type.getStatusCode();
         responseContext.setStatus(Response.Status.OK.getStatusCode());
         setEntity(String.valueOf(status));
      }

      public void getStatusInfoNotSet() {
         getStatusInfo();
      }

      public void getStringHeaders() {
         MultivaluedMap<String, String> map = responseContext.getStringHeaders();
         List<String> list = map.get(HEADER);
         setEntity(list == null ? NULL : list.size() == 1 ? list.iterator()
                 .next() : collectionToString(list));
      }

      public void hasEntity() {
         boolean has = responseContext.hasEntity();
         setEntity(String.valueOf(has));
      }

      public void hasLink() {
         boolean has = responseContext.hasLink(RELATION);
         setEntity(String.valueOf(has));
      }

      public void setEntity() {
         Annotation[] annotations = getClass().getAnnotations();
         MediaType type = MediaType.APPLICATION_SVG_XML_TYPE;
         responseContext.setEntity(ENTITY, annotations, type);
      }

      public void setEntityStream() {
         final OutputStream stream = responseContext.getEntityStream();
         OutputStream byteStream = new OutputStream() {
            @Override
            public void write(byte[] b) throws IOException {
               stream.write(ENTITY.getBytes());
               stream.write(b);
            }

            @Override
            public void write(int b) throws IOException {
               write(intToByteArray(b));
            }

            @Override
            public synchronized void write(byte[] b, int off, int len)
                    throws IOException {
               write(b);
            }

            public final byte[] intToByteArray(int value) {
               return new byte[] { (byte) (value & 0xff) };
            }
         };
         responseContext.setEntityStream(byteStream);
         StringBuilder sb = new StringBuilder(ENTITY.length() + 4);
         for (int i = 0; i < ENTITY.length() + 3; i += 2)
            sb.append("OK");
         setEntity(sb.toString());
      }

      public void setStatus() {
         String entity = (String) responseContext.getEntity();
         int status = Integer.parseInt(entity);
         responseContext.setStatus(status);
      }

      public void setStatusInfo() {
         String entity = (String) responseContext.getEntity();
         final int status = Integer.parseInt(entity);
         Response.StatusType type = new Response.StatusType() {

            @Override
            public int getStatusCode() {
               return status;
            }

            @Override
            public String getReasonPhrase() {
               return null;
            }

            @Override
            public Response.Status.Family getFamily() {
               return Response.Status.Family.familyOf(status);
            }
         };
         responseContext.setStatusInfo(type);
      }

      public void setStringBeanRuntime() {
         // pass
      }

      public void setOriginalRuntime() {
         // pass
      }
   }

   @Provider
   @Priority(100)
// reverse order, should be second
   public static class SecondResponseFilter extends TemplateFilter {
      @Override
      protected void operationMethodNotFound(String operation) {
         // the check is to apply on ResponseFilter only
         // here, it is usually not found.
      }

      public void getHeadersIsMutable() {
         MultivaluedMap<String, Object> headers = responseContext.getHeaders();
         if (assertTrue(headers.containsKey(HEADER), HEADER,
                 "header is not in header map"))
            return;
         setEntity(HEADER + " found as expected");
      }

      public void setEntity() {
         MediaType type = responseContext.getMediaType();
         if (assertTrue(MediaType.APPLICATION_SVG_XML_TYPE.equals(type),
                 "Unexpected mediatype", type))
            return;

         Annotation[] annotations = responseContext.getEntityAnnotations();
         for (Annotation annotation : annotations) {
            Class<?> clazz = annotation.annotationType();
            if (assertTrue(clazz == Provider.class
                    || clazz == Priority.class, "Annotation", clazz,
                    "was unexpected"))
               return;
         }
      }
   }

   @Path("resource")
   public static class Resource {

      @Context
      UriInfo info;

      @POST
      @Path("hasentity")
      public Response hasEntity(String entity) {
         Response.ResponseBuilder builder = createResponseWithHeader();
         if (entity != null && entity.length() != 0)
            builder = builder.entity(entity);
         Response response = builder.build();
         return response;
      }

      private Response.ResponseBuilder createResponseWithHeader() {
         // get value of @Path(value)
         List<PathSegment> segments = info.getPathSegments();
         PathSegment last = segments.get(segments.size() - 1);
         // convert the value to ContextOperation
         Response.ResponseBuilder builder = Response.ok();
         // set a header with ContextOperation so that the filter knows what to
         // do
         builder = builder.header(ResponseFilter.OPERATION, last.getPath()
                 .toUpperCase());
         return builder;
      }


   }




   static Client client;

   @BeforeClass
   public static void setup() throws Exception
   {
      addPerRequestResource(Resource.class);
      deployment.getProviderFactory().register(ResponseFilter.class);
      deployment.getProviderFactory().register(SecondResponseFilter.class);
      client = ClientBuilder.newClient();
   }

   @AfterClass
   public static void cleanup() throws Exception
   {
      client.close();
   }

   @Test
   public void testException()
   {
      System.out.println(new Exception().getStackTrace()[0].getMethodName());
   }

   @Test
   public void testHasEntity()
   {
      Response response = client.target(generateURL("/resource/hasentity")).request("*/*")
              .header("OPERATION", "hasentity").post(Entity.entity("entity", MediaType.WILDCARD_TYPE));
      Assert.assertEquals(200, response.getStatus());
      Assert.assertEquals(response.getMediaType(), MediaType.TEXT_PLAIN_TYPE);
      System.out.println(response.readEntity(String.class));
      response.close();

   }

}
