package ro.isdc.wro.http.support;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * Responseible for resolving a content based on file name.
 *
 * @author Alex Objelean
 */
public class ContentTypeResolver {
  private static final String DEFAULT_CONTENT_TYPE = "application/octet-stream";
  private static final Logger LOG = LoggerFactory.getLogger(ContentTypeResolver.class);
  private final static Map<String, String> defaultContentTypeMap = new HashMap<String, String>();
  private final static Set<String> requiresCharset = new HashSet<String>();

  static {
    defaultContentTypeMap.put("txt", "text/plain");
    defaultContentTypeMap.put("css", "text/css");
    defaultContentTypeMap.put("html", "text/html");
    defaultContentTypeMap.put("htm", "text/html");
    defaultContentTypeMap.put("xml", "application/xml");
    defaultContentTypeMap.put("xhtml", "application/xhtml+xml");
    defaultContentTypeMap.put("js", "application/javascript");
    defaultContentTypeMap.put("png", "image/png");
    defaultContentTypeMap.put("gif", "image/gif");
    defaultContentTypeMap.put("jpg", "image/jpeg");
    defaultContentTypeMap.put("jpeg", "image/jpeg");
    // font types
    defaultContentTypeMap.put("eot", "application/vnd.ms-fontobject");
    defaultContentTypeMap.put("otf", "application/x-font-opentype");

    requiresCharset.add("text/css");
    requiresCharset.add("text/html");
    requiresCharset.add("text/plain");
    requiresCharset.add("application/xml");
    requiresCharset.add("application/xhtml+xml");
    requiresCharset.add("application/javascript");
  }

  /**
   * Returns a valid HTTP contentType's for a given filename. It first relies on the custom defaultContentTypeMap and if
   * not found it will fall back to defaultFileTypeMap from javax.activation.FileTypeMap. Examples: - "somefile.css"
   * resolves to "text/css". - "somefile.js.png" resolves to "image/png" - "/blah/index.html resolves to "text/html".
   * <p/>
   * The implementation uses reflection to load <code>javax.activation.FileTypeMap</code> class (available in jdk6) in
   * order to be compatible with jdk5. If this class is not available, the default content type is returned.
   *
   * @param fileName
   *          with an filename extension
   * @return contentType
   */
  public static String get(final String fileName) {
    final String extension = FilenameUtils.getExtension(fileName).toLowerCase();
    if (defaultContentTypeMap.containsKey(extension)) {
      return defaultContentTypeMap.get(extension);
    }
    try {
      final Class<?> fileTypeMapClass = ClassLoader.getSystemClassLoader().loadClass("javax.activation.FileTypeMap");
      LOG.debug("using {} to resolve contentType", fileTypeMapClass.getName());
      final Object fileTypeMap = fileTypeMapClass.getMethod("getDefaultFileTypeMap").invoke(fileTypeMapClass);
      return (String) fileTypeMapClass.getMethod("getContentType", String.class).invoke(fileTypeMap, fileName);
    } catch (final Exception e) {
      LOG.debug("FileTypeMap is not available (probably jdk5 is used). Exception {}, with message: {}", e.getClass(),
          e.getMessage());
      LOG.debug("Will use default content type: {} for fileName: {}", DEFAULT_CONTENT_TYPE, fileName);
    }
    return DEFAULT_CONTENT_TYPE;
  }

  /**
   * Returns a valid HTTP contentType's for a given filename with charset. It first relies on the custom
   * defaultContentTypeMap and if not found it will fall back to defaultFileTypeMap from javax.activation.FileTypeMap.
   * Examples: - ("somefile.css", "UTF-8") resolves to "text/css"; charset=UTF-8". - ("somefile.js.png", "UTF-8")
   * resolves to "image/png" - ("/blah/index.html, "UTF-8") resolves to "text/html; charset=8"
   *
   * @param fileName
   *          with an filename extension
   * @param encoding
   *          which encoding to use
   * @return contentType
   */
  public static String get(final String fileName, final String encoding) {
    final String contentType = get(fileName);
    if (requiresCharset.contains(contentType)) {
      return contentType + "; charset=" + encoding;
    }
    return contentType;
  }
}