package jp.sourceforge.shovel.interceptor;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

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

import org.aopalliance.intercept.MethodInvocation;
import org.apache.struts.Globals;
import org.apache.struts.util.MessageResources;
import org.seasar.framework.aop.interceptors.ThrowsInterceptor;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.exception.SQLRuntimeException;
import org.seasar.framework.exception.SSQLException;

import jp.sourceforge.shovel.ErrorPageType;
import jp.sourceforge.shovel.exception.ApplicationException;
import jp.sourceforge.shovel.exception.ApplicationRuntimeException;
import jp.sourceforge.shovel.exception.ErrorPage;

import freemarker.template.TemplateException;
import static jp.sourceforge.shovel.CommonConst.*;

/**
 * エラーページの出力を振り分ける例外に積み直す
 */
public class HandleThrowableInterceptor extends ThrowsInterceptor {
    static final long serialVersionUID = -1L;
    static final ErrorPageType DEFAULT_ERROR_PAGE = ErrorPageType.HTML;
    
    S2Container container_;
    
    public void setContainer(S2Container container) {
        container_ = container;
    }
    S2Container getContainer() {
        return container_;
    }
    /**
     * エラー画面の出力を振り分ける例外の作成
     * 
     * @param cause     発生した例外
     * @return          エラー画面の出力を振り分ける例外
     */
    private ErrorPage createErrorPage(Throwable cause, MethodInvocation invocation) {
        //HttpServletRequest request = (HttpServletRequest)getContainer().getComponent(REQUEST_NAME);
        //filterにかけること前提に考える
        HttpServletRequest request = (HttpServletRequest)(invocation.getArguments()[0]);
        HttpServletResponse response = (HttpServletResponse)(invocation.getArguments()[1]);
        
        request.setAttribute("exceptionKey", Globals.EXCEPTION_KEY);
        ErrorPageType errorPageType = (ErrorPageType)request.getAttribute(ERROR_PAGE_TYPE);
        if (errorPageType == null) {
            errorPageType = DEFAULT_ERROR_PAGE;
        }
        StringBuilder component = new StringBuilder(errorPageType.getKey());
        component.append("ErrorPage");
        ErrorPage errorPage = (ErrorPage)getContainer().getComponent(component.toString());
        errorPage.setRequest(request);
        errorPage.setResponse(response);
        errorPage.initCause(cause);
        request.setAttribute("charset", "UTF-8");
        request.setAttribute(Globals.EXCEPTION_KEY, errorPage);
        return errorPage;
    }
    public void handleThrowable(ServletException exception, MethodInvocation invocation) throws ErrorPage {
        /**
         * org.seasar.framework.util.MethodUtilのinvokeの実行時
         * つまりS2のAOPやS2StrutsのPOJO Actionなどで発生した例外は
         * InvocationTargetRuntimeExceptionに包まれて上層に伝わる
         * filterは更にServletの上層であるためServletExceptionに
         * 包まれた例外で投げられる
         */
        Throwable rootCause = exception.getRootCause();
        Throwable cause = rootCause.getCause();
        
        //TODO 無限にエラーを吐き続けることがある場合の対策
        if (rootCause.getStackTrace().length > 1024) {
            return;
        }
        
        if (cause instanceof ApplicationException) {
            handleThrowable((ApplicationException)cause, invocation);
        } else if (rootCause instanceof ApplicationRuntimeException) {
            handleThrowable((ApplicationRuntimeException)rootCause, invocation);
        } else if (rootCause instanceof TemplateException) {
            handleThrowable((TemplateException)rootCause, invocation);
//TODO
//        } else if (rootCause instanceof ParseException) {
//            handleThrowable((ParseException)rootCause, invocation);
        } else if(rootCause instanceof SQLRuntimeException) {
            handleThrowable((SQLRuntimeException)rootCause, invocation);
        } else {
            handleThrowable((Exception)rootCause, invocation);
        }
    }
    /**
     * 製品が管理するエラー
     * @param cause
     * @param invocation
     * @throws ErrorPage
     */
    public void handleThrowable(ApplicationException cause, MethodInvocation invocation) throws ErrorPage {
        ErrorPage ep = createErrorPage(cause, invocation);
        ep.setErrorCode(cause.getBundle(), cause.getKey());
        ep.setOutputErrorCode(cause.getOutputErrorCode());
        ep.setCauseArgs(cause.getCauseArgs());
        ep.setCounterMeasureArgs(cause.getCounterMeasureArgs());
        ep.setDeveloperInfoArgs(cause.getDeveloperInfoArgs());
        ep.setDiagnosisArgs(cause.getDiagnosisArgs());
        throw ep;
    }
    /**
     * 製品ランタイムエラー（使ってるとこあったか？）
     * @param cause
     * @param invocation
     * @throws ErrorPage
     */
    public void handleThrowable(ApplicationRuntimeException cause, MethodInvocation invocation) throws ErrorPage {
        ErrorPage ep = createErrorPage(cause, invocation);
        ep.setErrorCode(cause.getBundle(), cause.getKey());
        ep.setOutputErrorCode(cause.getOutputErrorCode());
        ep.setCauseArgs(cause.getCauseArgs());
        ep.setCounterMeasureArgs(cause.getCounterMeasureArgs());
        ep.setDeveloperInfoArgs(cause.getDeveloperInfoArgs());
        ep.setDiagnosisArgs(cause.getDiagnosisArgs());
        throw ep;
    }
    //TODO bean message の引数に引き渡す値に特殊エスケープ処理を施す
    //引数が{}で包まれる文字列を含んでいると再帰的に置換しようとしてエラーに。。
    String escapeTemplate(String text) {
        StringBuilder buf = new StringBuilder("");
        Matcher matcher = Pattern.compile("\\{([^}]+)\\}").matcher(text);
        int i = 0;
        while (matcher.find()) {
            buf.append(text.substring(i, matcher.start(1) - 1));
            buf.append("&#123");
            buf.append(matcher.group(1));
            buf.append("&#125");
            i = matcher.end() + 1;
        }
        if (text.length() > i) {
            buf.append(text.substring(i));
        }
        return buf.toString();
    }
    /**
     * テンプレートエラー
     * @param cause
     * @param invocation
     * @throws ErrorPage
     */
    public void handleThrowable(TemplateException cause, MethodInvocation invocation) throws ErrorPage {
        ErrorPage ep = createErrorPage(cause, invocation);
        ep.setErrorCode("application", 101);
        String msg = escapeTemplate(cause.getMessage());
        ep.setCauseArgs(msg.replaceAll("\r?\n", "<br />"));
        msg = escapeTemplate(cause.getFTLInstructionStack());
        ep.setDeveloperInfoArgs(msg.replaceAll("\r?\n", "<br />"));
        throw ep;
    }
    /**
     * SQLエラー
     * 
     * @param cause
     * @param invocation
     * @throws ErrorPage
     */
    public void handleThrowable(SQLRuntimeException cause, MethodInvocation invocation) throws ErrorPage {
        MessageResources resources = MessageResources.getMessageResources("jp.sourceforge.shovel.error.db");
        Class<?>[] classes = cause.getCause().getClass().getClasses();
        for (Class<?> klass : classes) {
            if (klass.getName().compareToIgnoreCase("SSQLException") == 0) {
                SSQLException sqlex = (SSQLException)cause.getCause();
                String message = resources.getMessage(
                        String.format("%05d.diagnosis", sqlex.getErrorCode()));
                int errorCode = sqlex.getErrorCode(); 
                if (message == null) {
                    errorCode = 99999;  //Unsupported
                }

                ErrorPage ep = createErrorPage(cause, invocation);
                ep.setErrorCode("database", errorCode);
                Throwable t = sqlex.getCause();
                ep.setCauseArgs(sqlex.getSQLState(), t.getMessage(),
                        String.valueOf(sqlex.getErrorCode()));
                ep.setDeveloperInfoArgs(sqlex.getSql());
                throw ep;
            }
        }
        ErrorPage ep = createErrorPage(cause, invocation);
        ep.setErrorCode("application", 0);
        ep.setCauseArgs(cause.getSimpleMessage());
        throw ep;
    }
    /**
     * 上記以外の内部エラー
     * 
     * @param cause
     * @param invocation
     * @throws ErrorPage
     */
    public void handleThrowable(Exception cause, MethodInvocation invocation) throws ErrorPage {
        ErrorPage ep = createErrorPage(cause, invocation);
        ep.setErrorCode("application", 0);
        ep.setCauseArgs(cause.getMessage());
        throw ep;
    }
}
