package com.mossle.core.servlet;

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

import java.util.zip.GZIPOutputStream;

import javax.activation.MimetypesFileTypeMap;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.mossle.core.util.ServletUtils;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class StaticContentFilter implements Filter {
    private static Logger logger = LoggerFactory
            .getLogger(StaticContentFilter.class);

    /** 需要被Gzip压缩的Mime类型. */
    public static final String[] GZIP_MIME_TYPES = { "text/html",
            "application/xhtml+xml", "text/plain", "text/css",
            "text/javascript", "application/x-javascript", "application/json" };

    /** 需要被Gzip压缩的最小文件大小. */
    public static final int GZIP_MINI_LENGTH = 512;
    private MimetypesFileTypeMap mimetypesFileTypeMap;
    private long expiresSeconds;
    private FilterConfig filterConfig;

    public void init(FilterConfig filterConfig) throws ServletException {
        this.filterConfig = filterConfig;
        // 初始化mimeTypes, 默认缺少css的定义,添加之.
        mimetypesFileTypeMap = new MimetypesFileTypeMap();
        mimetypesFileTypeMap.addMimeTypes("text/css css");
    }

    public void destroy() {
        // destroy
    }

    public void doFilter(ServletRequest req, ServletResponse res,
            FilterChain filterChain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        // 获取请求内容的基本信息.
        String requestUri = request.getRequestURI();
        String contentPath = requestUri.substring(request.getContextPath()
                .length());

        ContentInfo contentInfo = getContentInfo(contentPath);

        if (contentInfo.getFile().isDirectory()) {
            if (requestUri.endsWith("/")) {
                response.sendRedirect(requestUri + "index.html");
            } else {
                response.sendRedirect(requestUri + "/index.html");
            }

            return;
        }

        if (!contentInfo.getFile().exists()) {
            response.sendError(HttpServletResponse.SC_NOT_FOUND);

            return;
        }

        // 根据Etag或ModifiedSince Header判断客户端的缓存文件是否有效, 如仍有效则设置返回码为304,直接返回.
        if (!ServletUtils.checkIfModifiedSince(request, response,
                contentInfo.getLastModified())
                || !ServletUtils.checkIfNoneMatchEtag(request, response,
                        contentInfo.getEtag())) {
            return;
        }

        // 设置Etag/过期时间
        ServletUtils.setExpiresHeader(response, ServletUtils.ONE_YEAR_SECONDS);
        ServletUtils.setLastModifiedHeader(response,
                contentInfo.getLastModified());
        ServletUtils.setEtag(response, contentInfo.getEtag());

        // 设置MIME类型
        response.setContentType(contentInfo.getMimeType());

        // 设置弹出下载文件请求窗口的Header
        if (request.getParameter("download") != null) {
            ServletUtils.setFileDownloadHeader(request, response,
                    contentInfo.getFileName());
        }

        // 构造OutputStream
        OutputStream output;

        // if (checkAccetptGzip(request) && contentInfo.isNeedGzip()) {
        // 使用压缩传输的outputstream, 使用http1.1 chunked编码不设置content-length.
        // output = buildGzipOutputStream(response);
        // } else {
        // 使用普通outputstream, 设置content-length.
        response.setContentLength(contentInfo.length);
        output = response.getOutputStream();
        // }

        // 高效读取文件内容并输出,然后关闭input file
        FileUtils.copyFile(contentInfo.getFile(), output);
        output.flush();
    }

    /**
     * 检查浏览器客户端是否支持gzip编码.
     */
    private static boolean checkAccetptGzip(HttpServletRequest request) {
        // Http1.1 header
        String acceptEncoding = request.getHeader("Accept-Encoding");

        return StringUtils.contains(acceptEncoding, "gzip");
    }

    /**
     * 设置Gzip Header并返回GZIPOutputStream.
     */
    private OutputStream buildGzipOutputStream(HttpServletResponse response)
            throws IOException {
        response.setHeader("Content-Encoding", "gzip");
        response.setHeader("Vary", "Accept-Encoding");

        return new GZIPOutputStream(response.getOutputStream());
    }

    /**
     * 创建Content基本信息.
     */
    private ContentInfo getContentInfo(String contentPath) {
        ContentInfo contentInfo = new ContentInfo();

        String realFilePath = this.findRealFilePath(contentPath);
        logger.debug("realFilePath {}", realFilePath);

        File file = new File(realFilePath);

        contentInfo.setFile(file);
        contentInfo.setContentPath(contentPath);
        contentInfo.setFileName(file.getName());
        contentInfo.setLength((int) file.length());

        contentInfo.setLastModified(file.lastModified());
        contentInfo.setEtag("W/\"" + contentInfo.lastModified + "\"");

        contentInfo.setMimeType(mimetypesFileTypeMap
                .getContentType(contentInfo.fileName));

        if ((contentInfo.length >= GZIP_MINI_LENGTH)
                && ArrayUtils.contains(GZIP_MIME_TYPES,
                        contentInfo.getMimeType())) {
            contentInfo.setNeedGzip(true);
        } else {
            contentInfo.setNeedGzip(false);
        }

        return contentInfo;
    }

    public String findRealFilePath(String contentPath) {
        String realFilePath = filterConfig.getServletContext().getRealPath(
                contentPath);

        return realFilePath;
    }

    public void setExpiresSeconds(long expiresSeconds) {
        this.expiresSeconds = expiresSeconds;
    }

    static class ContentInfo {
        private String contentPath;
        private File file;
        private String fileName;
        private int length;
        private String mimeType;
        private long lastModified;
        private String etag;
        private boolean needGzip;

        public String getContentPath() {
            return this.contentPath;
        }

        public void setContentPath(String contentPath) {
            this.contentPath = contentPath;
        }

        public File getFile() {
            return this.file;
        }

        public void setFile(File file) {
            this.file = file;
        }

        public String getFileName() {
            return this.fileName;
        }

        public void setFileName(String fileName) {
            this.fileName = fileName;
        }

        public int getLength() {
            return this.length;
        }

        public void setLength(int length) {
            this.length = length;
        }

        public String getMimeType() {
            return this.mimeType;
        }

        public void setMimeType(String mimeType) {
            this.mimeType = mimeType;
        }

        public long getLastModified() {
            return this.lastModified;
        }

        public void setLastModified(long lastModified) {
            this.lastModified = lastModified;
        }

        public String getEtag() {
            return this.etag;
        }

        public void setEtag(String etag) {
            this.etag = etag;
        }

        public boolean isNeedGzip() {
            return this.needGzip;
        }

        public void setNeedGzip(boolean needGzip) {
            this.needGzip = needGzip;
        }
    }
}
