/**
 * $RCSfile$
 * $Revision: $
 * $Date: $
 *
 * Copyright (C) 2005-2008 Jive Software. All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution, or a commercial license
 * agreement with Jive.
 */
package org.jivesoftware.openfire.http;

import org.jivesoftware.util.*;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactory;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.zip.GZIPOutputStream;
import java.io.*;

/**
 * Combines and serves resources, such as javascript or css files.
 */
public class ResourceServlet extends HttpServlet {
//    private static String suffix = "";    // Set to "_src" to use source version
    private static long expiresOffset = 3600 * 24 * 10;	// 10 days util client cache expires
    private boolean debug = false;
    private boolean disableCompression = false;
    private static Cache<String, byte[]> cache = CacheFactory.createCache("Javascript Cache");

    public void init(ServletConfig config) throws ServletException {
        super.init(config);

        debug = Boolean.valueOf(config.getInitParameter("debug"));
        disableCompression = Boolean.valueOf(config.getInitParameter("disableCompression"));
    }

    public void service(HttpServletRequest request, HttpServletResponse response) {
        boolean compress = false;

        boolean javascript = request.getRequestURI().endsWith("scripts/");

        if (!disableCompression) {
            if (request.getHeader("accept-encoding") != null &&
                request.getHeader("accept-encoding").indexOf("gzip") != -1) {
                compress = true;
            }
            else if (request.getHeader("---------------") != null) {
                // norton internet security
                compress = true;
            }
        }

        if(javascript) {
            response.setHeader("Content-type", "text/javascript");
        }
        else {
            response.setHeader("Content-type", "text/css");
        }
        response.setHeader("Vary", "Accept-Encoding"); // Handle proxies

        if (!debug) {
            DateFormat formatter = new SimpleDateFormat("d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
            formatter.setTimeZone(TimeZone.getTimeZone("GMT"));
            response.setHeader("Expires", formatter.format(new Date(System.currentTimeMillis()
                    + expiresOffset)));
            response.setHeader("Cache-Control", "max-age=" + expiresOffset);
        }
        else {
            response.setHeader("Expires", "1");
            compress = false;
        }
        OutputStream out = null;
        InputStream in = null;

        try {
            byte[] content;
            String cacheKey = String.valueOf(compress + " " + javascript);
            content = cache.get(cacheKey);
            if (javascript && (debug || content == null)) {
                content = getJavaScriptContent(compress);
                cache.put(cacheKey, content);
            }
            else if(!javascript && content == null) {

            }

            response.setContentLength(content.length);
            if (compress) {
                response.setHeader("Content-Encoding", "gzip");
            }

            // Write the content out
            in = new ByteArrayInputStream(content);
            out = response.getOutputStream();

            // Use a 128K buffer.
            byte[] buf = new byte[128*1024];
            int len;
            while ((len=in.read(buf)) != -1) {
                out.write(buf, 0, len);
            }
            out.flush();
        }
        catch (IOException e) {
            Log.error(e);
        }
        finally {
            try { if (in != null) { in.close(); } } catch (Exception ignored) { /* ignored */ }
            try { if (out != null) { out.close(); } } catch (Exception ignored) { /* ignored */ }
        }
    }

    private static byte[] getJavaScriptContent(boolean compress) throws IOException
    {
        StringWriter writer = new StringWriter();
        for(String file : getJavascriptFiles()) {
            writer.write(getJavaScriptFile(file));
        }
        
        if (compress) {
            ByteArrayOutputStream baos = null;
            GZIPOutputStream gzos = null;
            try {
                baos = new ByteArrayOutputStream();
                gzos = new GZIPOutputStream(baos);

                gzos.write(writer.toString().getBytes());
                gzos.finish();
                gzos.flush();
                gzos.close();

                return baos.toByteArray();
            }
            finally {
                try { if (gzos != null) { gzos.close(); } }
                catch (Exception ignored) { /* ignored */ }
                try { if (baos != null) { baos.close(); } }
                catch (Exception ignored) { /* ignored */ }
            }
        }
        else {
            return writer.toString().getBytes();
        }
    }

    private static Collection<String> getJavascriptFiles() {
        return Arrays.asList("prototype.js", "getelementsbyselector.js", "sarissa.js",
                "connection.js", "yahoo-min.js", "dom-min.js", "event-min.js", "dragdrop-min.js",
                "yui-ext.js", "spank.js");
    }

    private static String getJavaScriptFile(String path) {
        StringBuilder sb = new StringBuilder();
        InputStream in = null;
        InputStreamReader isr = null;
        BufferedReader br = null;
        try {
            in = getResourceAsStream(path);
            if (in == null) {
                Log.error("Unable to find javascript file: '" + path + "' in classpath");
                return "";
            }

            isr = new InputStreamReader(in, "ISO-8859-1");
            br = new BufferedReader(isr);
            String line;
            while ((line = br.readLine()) != null) {
                sb.append(line.trim()).append('\n');
            }
        }
        catch (Exception e) {
            Log.error("Error loading JavaScript file: '" + path + "'.", e);
        }
        finally {
            try { if (br != null) { br.close(); } } catch (Exception ignored) { /* ignored */ }
            try { if (isr != null) { isr.close(); } } catch (Exception ignored) { /* ignored */ }
            try { if (in != null) { in.close(); } } catch (Exception ignored) { /* ignored */ }
        }

        return sb.toString();
    }

    private static InputStream getResourceAsStream(String resourceName) {
        File file = new File(JiveGlobals.getHomeDirectory() + File.separator +
                "resources" + File.separator + "spank" + File.separator + "scripts", resourceName);
        try {
            return new FileInputStream(file);
        }
        catch (FileNotFoundException e) {
            return null;
        }
    }
}