package ro.isdc.wro.manager;

import java.io.IOException;
import java.io.OutputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ro.isdc.wro.WroRuntimeException;
import ro.isdc.wro.cache.CacheKey;
import ro.isdc.wro.cache.CacheStrategy;
import ro.isdc.wro.cache.CacheValue;
import ro.isdc.wro.cache.factory.CacheKeyFactory;
import ro.isdc.wro.config.Context;
import ro.isdc.wro.config.ReadOnlyContext;
import ro.isdc.wro.config.jmx.WroConfiguration;
import ro.isdc.wro.http.support.HttpHeader;
import ro.isdc.wro.model.group.Inject;
import ro.isdc.wro.model.resource.ResourceType;
import ro.isdc.wro.util.WroUtil;


/**
 * Encapsulates the bundle creation.
 *
 * @author Alex Objelean
 * @created 18 Jun 2012
 * @since 1.4.7
 */
public class ResourceBundleProcessor {
  private static final Logger LOG = LoggerFactory.getLogger(ResourceBundleProcessor.class);
  /**
   * A cacheStrategy used for caching processed results. <GroupName, processed result>.
   */
  @Inject
  private CacheStrategy<CacheKey, CacheValue> cacheStrategy;
  @Inject
  private ReadOnlyContext context;
  @Inject
  private CacheKeyFactory cacheKeyFactory;


  /**
   * Write to stream the content of the processed resource bundle.
   */
  public void serveProcessedBundle()
      throws IOException {
    final WroConfiguration configuration = context.getConfig();
    final HttpServletRequest request = context.getRequest();
    final HttpServletResponse response = context.getResponse();

    OutputStream os = null;
    try {

      final CacheKey cacheKey = getSafeCacheKey(request);
      initAggregatedFolderPath(request, cacheKey.getType());
      final CacheValue cacheValue = cacheStrategy.get(cacheKey);

      // TODO move ETag check in wroManagerFactory
      final String ifNoneMatch = request.getHeader(HttpHeader.IF_NONE_MATCH.toString());

      // enclose etag value in quotes to be compliant with the RFC
      final String etagValue = String.format("\"%s\"", cacheValue.getHash());

      if (etagValue != null && etagValue.equals(ifNoneMatch)) {
        LOG.debug("ETag hash detected: {}. Sending {} status code", etagValue, HttpServletResponse.SC_NOT_MODIFIED);
        response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
        // because we cannot return null, return a stream containing nothing.
        // TODO close output stream?
        return;
      }
      /**
       * Set contentType before actual content is written, solves <br/>
       * <a href="http://code.google.com/p/wro4j/issues/detail?id=341">issue341</a>
       */
      response.setContentType(cacheKey.getType().getContentType() + "; charset=" + configuration.getEncoding());
      // set ETag header
      response.setHeader(HttpHeader.ETAG.toString(), etagValue);

      os = response.getOutputStream();
      if (cacheValue.getRawContent() != null) {
        // use gziped response if supported & Set content length based on gzip flag
        if (isGzipAllowed()) {
          response.setContentLength(cacheValue.getGzippedContent().length);
          // add gzip header and gzip response
          response.setHeader(HttpHeader.CONTENT_ENCODING.toString(), "gzip");
          response.setHeader("Vary", "Accept-Encoding");
          IOUtils.write(cacheValue.getGzippedContent(), os);
        } else {
          //using getRawContent().length() is not the same and can return 2Bytes smaller size.
          response.setContentLength(cacheValue.getRawContent().getBytes(configuration.getEncoding()).length);
          IOUtils.write(cacheValue.getRawContent(), os, configuration.getEncoding());
        }
      }
    } finally {
      if (os != null) {
        IOUtils.closeQuietly(os);
      }
    }
  }

  private CacheKey getSafeCacheKey(final HttpServletRequest request) {
    final CacheKey cacheKey = cacheKeyFactory.create(request);
    if (cacheKey == null) {
      throw new WroRuntimeException("Cannot build valid CacheKey from request: " + request.getRequestURI());
    }
    return cacheKey;
  }

  private boolean isGzipAllowed() {
    return context.getConfig().isGzipEnabled() && WroUtil.isGzipSupported(context.getRequest());
  }

  /**
   * Set the aggregatedFolderPath if required.
   */
  private void initAggregatedFolderPath(final HttpServletRequest request, final ResourceType type) {
    if (ResourceType.CSS == type && context.getAggregatedFolderPath() == null) {
      final String requestUri = request.getRequestURI();
      final String cssFolder = StringUtils.removeEnd(requestUri, FilenameUtils.getName(requestUri));
      final String aggregatedFolder = StringUtils.removeStart(cssFolder, request.getContextPath());
      LOG.debug("set aggregatedFolderPath: {}", aggregatedFolder);
      Context.get().setAggregatedFolderPath(aggregatedFolder);
    }
  }
}
