package org.infinispan.rest.logging;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.ext.Provider;

import org.infinispan.util.logging.LogFactory;

import io.netty.channel.ChannelHandlerContext;

/**
 * Logging filter that can be used to output requests in a similar fashion to HTTPD log output
 *
 * @author wburns
 * @since 8.3
 */
@Provider
public class RestAccessLoggingHandler implements ContainerResponseFilter, ContainerRequestFilter {
   private final static Log log = LogFactory.getLog(RestAccessLoggingHandler.class, Log.class);

   private final static String NANO_TIME = "NanoTime";

   @Context
   private ChannelHandlerContext context;

   @Override
   public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) throws IOException {
      // IP
      String remoteAddress = context.channel().remoteAddress().toString();
      // Date
      String timeString = requestContext.getHeaderString(NANO_TIME);
      long startNano;
      if (timeString != null) {
         startNano = Long.parseLong(requestContext.getHeaderString(NANO_TIME));
      } else {
         startNano = 0;
      }
      // Request method | path | protocol
      String requestMethod = requestContext.getMethod();
      String uri = requestContext.getUriInfo().getPath();
      // Status code
      int status = responseContext.getStatus();
      // Body request size
      int requestSize = requestContext.getLength();
      // Body response Size - usually -1 so we calculate below
      int responseSize = responseContext.getLength();
      // Netty doesn't usually set the CONTENT_LENGTH on response - check if we can
      if (responseSize == -1 && responseContext.hasEntity()) {
         Object entity = responseContext.getEntity();
         if (entity instanceof byte[]) {
            responseSize = ((byte[]) entity).length;
            responseContext.getHeaders().addFirst(HttpHeaders.CONTENT_LENGTH, responseSize);
         }
      }
      // Response time
      long responseTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNano);

      log.tracef("%s [%s] \"%s %s\" %s %d %d %d ms", remoteAddress, responseTime, requestMethod, uri, status, requestSize,
            responseSize, responseTime);
   }

   @Override
   public void filter(ContainerRequestContext requestContext) throws IOException {
      int requestSize = requestContext.getLength();
      if (requestSize == -1) {
         requestSize = requestContext.getEntityStream().available();
         requestContext.getHeaders().putSingle(HttpHeaders.CONTENT_LENGTH, Integer.toString(requestSize));
      }
      // Set the starting time
      requestContext.getHeaders().putSingle(NANO_TIME, Long.toString(System.nanoTime()));
   }
}
