package jp.sourceforge.shovel.servlet.freemarker;

import static org.seasar.framework.container.ContainerConstants.*;
import static jp.sourceforge.shovel.CommonConst.*;

import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import net.arnx.jsonic.JSON;

import org.apache.commons.lang.ArrayUtils;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;
import org.seasar.framework.util.ResourceUtil;

import freemarker.ext.servlet.FreemarkerServlet;
import freemarker.template.Configuration;
import freemarker.template.ObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;

import jp.sourceforge.shovel.entity.TokenProcessorWrapper;

public class FreemarkerServletWrapper extends FreemarkerServlet {
    static final long serialVersionUID = -1L;
    
    public void init() throws ServletException {
        super.init();
        Configuration config = getConfiguration();
        config.setTemplateLoader(new WebappTemplateLoaderExt(this.getServletContext(), getTemplatePath()));
    }
    
    protected boolean preTemplateProcess(HttpServletRequest request, HttpServletResponse response,
            Template template, TemplateModel data) throws ServletException, IOException {
        //出力文字コードの書き換え
        String encoding = (String)request.getAttribute(OUTPUT_ENCODING);
        if( encoding == null ) {
            //TODO デフォルトのエンコーディングはutf-8？
            encoding = "UTF-8";
        }
        response.setCharacterEncoding(encoding);
        //ContentTypeの書き換え
        String mimeType = (String)request.getAttribute(OUTPUT_MIMETYPE);
        if( mimeType != null ) {
            response.setContentType(mimeType + ";charset=" + encoding);
        }
        
        //定数の割り当て
        S2Container container = SingletonS2ContainerFactory.getContainer();
        Properties props = (Properties)container.getComponent(COMMON_PROPERTIES);
        boolean debug = Boolean.parseBoolean(props.getProperty("debug", "false"));
        String webRoot = props.getProperty(KEY_WEBROOT, "");
        String appPath = request.getContextPath();
        StringBuffer url = request.getRequestURL();
        String baseUrl = "";
        try {
            URI uri = new URI(url.toString());
            uri = new URI(uri.getScheme(), null, uri.getHost(),
                    uri.getPort(), null, null, null);
            baseUrl = uri.toString();
        } catch (URISyntaxException e) {
            //TODO とりあえず握りつぶしとく
        }
        request.setAttribute("debug", debug);
        request.setAttribute(KEY_WEBROOT, webRoot);
        request.setAttribute("app_path", appPath);
        request.setAttribute("base_url", baseUrl);
        
        //表示行数
        ServletContext context = (ServletContext)container.getComponent(SERVLET_CONTEXT_NAME);
        if (context.getAttribute("viewLinesPresets") == null) {
            int[] viewLinesPresets;
            try {
                String serializedPreset = props.getProperty("viewLinesPreset", "5,10,15,20,25,30");
                String[] strPresets = serializedPreset.split(",");
                List<Integer> presetList = new ArrayList<Integer>();
                for (String strPreset : strPresets) {
                    presetList.add(Integer.parseInt(strPreset));
                }
                Integer[] presets = presetList.toArray(new Integer[presetList.size()]);
                viewLinesPresets = ArrayUtils.toPrimitive(presets);
            } catch (Exception e) {
                viewLinesPresets = new int[] {5, 10, 15, 20, 25, 30};
            }
            context.setAttribute("viewLinesPresets", viewLinesPresets);
        }
        
        //リリース時はバージョン番号
        //String version = (String)props.getProperty("version");
        String version = (String)container.getComponent("version");
        if (debug || version == null) {
            long time = Calendar.getInstance().getTimeInMillis();
            version = String.valueOf(time);
        }
        request.setAttribute("version", version);
        
        //レスポンスバウンダリ
        String salt = String.valueOf(System.currentTimeMillis());
        String token = new TokenProcessorWrapper().generateToken(salt);
        String boundary = RESPONSE_BOUNDARY + token;
        request.setAttribute(BOUNDARY, boundary);
        response.setHeader(RESPONSE_BOUNDARY_HEADER, boundary);
        
        //StrutsではSessionにあるLocaleを最優先としているらしい
        //それを変えればよいのかな？
        //http://rumble-jp.sourceforge.jp/wiki/index.php?%5B%5BRumble-JP1.0%2F%B9%F1%BA%DD%B2%BD%2F%C9%BD%BC%A8%B8%C0%B8%EC%5D%5D
        //http://jfut.featia.net/diary/20060423.html
        HttpSession session = request.getSession(true);
        Locale requestLocale = request.getLocale();
        Locale sessionLocale = (Locale)session.getAttribute(org.apache.struts.Globals.LOCALE_KEY);
        if (requestLocale != sessionLocale) {
            session.setAttribute(org.apache.struts.Globals.LOCALE_KEY, requestLocale);
        }
        request.setAttribute("localeKey", org.apache.struts.Globals.LOCALE_KEY);
        
        //JavaScriptで使うメッセージリソース
        String path = "jp/sourceforge/shovel/message/javascript.properties";
        if (sessionLocale.getLanguage() != Locale.ENGLISH.getLanguage()) {
            StringBuilder pathBuilder = new StringBuilder("jp/sourceforge/shovel/message/javascript_");
            pathBuilder.append(sessionLocale.getLanguage());
            pathBuilder.append(".properties");
            if (ResourceUtil.isExist(pathBuilder.toString())) {
                path = pathBuilder.toString();
            }
        }
        props = ResourceUtil.getProperties(path);
        request.setAttribute("javascriptResources", JSON.encode(props));
        
        return super.preTemplateProcess(request, response, template, data);
    }
    
    Configuration config_;
    
    protected Configuration createConfiguration() {
        config_ = super.createConfiguration();
        return config_;
    }
    
    ObjectWrapper wrapper_;
    
    protected ObjectWrapper createObjectWrapper() {
        wrapper_ = super.createObjectWrapper();
        
        S2Container container = SingletonS2ContainerFactory.getContainer();
        Map applicationScope = (Map)container.getComponent(APPLICATION_SCOPE);
        //TODO 起動時にFreemarkerのサーブレットをアプリケーションスコープに積む反則技
        applicationScope.put("freemarkerServlet", this);
        
        return wrapper_;
    }

    public StringBuffer processNow(String path, HttpServletRequest request, HttpServletResponse response) {
        ServletContext servletContext = getServletContext();
        StringWriter writer = new StringWriter();
        
        try {
            Template template = config_.getTemplate(path);
            TemplateModel model = createModel(wrapper_, servletContext, request, response);
            
            // Give subclasses a chance to hook into preprocessing
            if (preTemplateProcess(request, response, template, model)) {
                try {
                    // Process the template
                    template.process(model, writer);
                } finally {
                    // Give subclasses a chance to hook into postprocessing
                    postTemplateProcess(request, response, template, model);
                }
            }
        } catch (IOException ex) {
        } catch (TemplateException ex) {
        } catch (ServletException ex) {
        }

        return writer.getBuffer();
    }
}
