/*
 * Copyright 2004-2012 the Seasar Foundation and the Others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.seasar.mayaa.impl.engine;

import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import org.seasar.mayaa.cycle.ServiceCycle;
import org.seasar.mayaa.cycle.script.CompiledScript;
import org.seasar.mayaa.engine.Engine;
import org.seasar.mayaa.engine.Page;
import org.seasar.mayaa.engine.Template;
import org.seasar.mayaa.engine.TemplateRenderer;
import org.seasar.mayaa.engine.processor.ChildEvaluationProcessor;
import org.seasar.mayaa.engine.processor.IterationProcessor;
import org.seasar.mayaa.engine.processor.ProcessStatus;
import org.seasar.mayaa.engine.processor.ProcessorTreeWalker;
import org.seasar.mayaa.engine.processor.TemplateProcessor;
import org.seasar.mayaa.engine.processor.TryCatchFinallyProcessor;
import org.seasar.mayaa.impl.CONST_IMPL;
import org.seasar.mayaa.impl.cycle.CycleUtil;
import org.seasar.mayaa.impl.engine.processor.ElementProcessor;
import org.seasar.mayaa.impl.engine.specification.SpecificationUtil;
import org.seasar.mayaa.impl.provider.ProviderUtil;
import org.seasar.mayaa.impl.util.StringUtil;
import org.seasar.mayaa.source.SourceDescriptor;

/**
 * @author Masataka Kurihara (Gluegent, Inc.)
 */
public class RenderUtil implements CONST_IMPL {

    private static final ProcessStatus SKIP_BODY =
            ProcessStatus.SKIP_BODY;
    private static final ProcessStatus EVAL_BODY_INCLUDE =
            ProcessStatus.EVAL_BODY_INCLUDE;
    private static final ProcessStatus SKIP_PAGE =
            ProcessStatus.SKIP_PAGE;
    private static final ProcessStatus EVAL_PAGE =
            ProcessStatus.EVAL_PAGE;
    private static final ProcessStatus EVAL_BODY_AGAIN =
            ProcessStatus.EVAL_BODY_AGAIN;
    private static final ProcessStatus EVAL_BODY_BUFFERED =
            ProcessStatus.EVAL_BODY_BUFFERED;

    private RenderUtil() {
        // no instantiation.
    }

    public static boolean isEvaluation(TemplateProcessor current) {
        return current instanceof ChildEvaluationProcessor
                && ((ChildEvaluationProcessor) current).isChildEvaluation();
    }

    public static ChildEvaluationProcessor getEvaluation(
            TemplateProcessor current) {
        return (ChildEvaluationProcessor) current;
    }

    public static boolean isIteration(TemplateProcessor current) {
        return current instanceof IterationProcessor
                && ((IterationProcessor) current).isIteration();
    }

    public static IterationProcessor getIteration(
            TemplateProcessor current) {
        return (IterationProcessor) current;
    }

    public static boolean isDuplicated(TemplateProcessor current) {
        return current instanceof ElementProcessor
                && ((ElementProcessor) current).isDuplicated();
    }

    public static boolean isTryCatchFinally(TemplateProcessor current) {
        if (current instanceof TryCatchFinallyProcessor) {
            TryCatchFinallyProcessor tryCatchFinallyProcessor
                                        = (TryCatchFinallyProcessor) current;
            return tryCatchFinallyProcessor.canCatch();
        }
        return false;
    }

    public static TryCatchFinallyProcessor getTryCatchFinally(
            TemplateProcessor current) {
        return (TryCatchFinallyProcessor) current;
    }

    public static void saveToCycle(ProcessorTreeWalker current) {
        ServiceCycle cycle = CycleUtil.getServiceCycle();
        cycle.setProcessor(current);
        if (current instanceof TemplateProcessor) {
            TemplateProcessor proc = (TemplateProcessor) current;
            cycle.setOriginalNode(proc.getOriginalNode());
            cycle.setInjectedNode(proc.getInjectedNode());
        } else if (current instanceof Template) {
            Template temp = (Template) current;
            cycle.setOriginalNode(temp);
            cycle.setInjectedNode(temp);
        }
    }

    // main rendering method
    public static ProcessStatus renderTemplateProcessor(
            Page topLevelPage, TemplateProcessor current) {
        if (current == null) {
            throw new IllegalArgumentException();
        }
        saveToCycle(current);
        ServiceCycle cycle = CycleUtil.getServiceCycle();
        ProcessStatus ret = EVAL_PAGE;
        boolean buffered = false;

        setCurrentProcessor(current);
        Engine engine = ProviderUtil.getEngine();
        SpecificationUtil.execEvent(engine, QM_BEFORE_RENDER_PROCESSOR);
        try {
            SpecificationUtil.startScope(current.getVariables());
            ProcessStatus startRet = EVAL_BODY_INCLUDE;
            startRet = current.doStartProcess(topLevelPage);
            if (startRet == SKIP_PAGE) {
                return SKIP_PAGE;
            }
            if (startRet == EVAL_BODY_BUFFERED && isEvaluation(current)) {
                buffered = true;
                ChildEvaluationProcessor processor = getEvaluation(current);
                processor.setBodyContent(cycle.getResponse().pushNoFlushWriter());
                processor.doInitChildProcess();
            }
            if (startRet == EVAL_BODY_INCLUDE
                    || startRet == EVAL_BODY_BUFFERED) {
                try {
                    buffered = renderTemplateProcessorChildren(
                            topLevelPage, current, buffered);
                } catch (SkipPageException e) {
                    return SKIP_PAGE;
                }
            }
            saveToCycle(current);
            if (buffered) {
                cycle.getResponse().popWriter();
                getIteration(current).doAfterChildProcess();
            }
            ret = current.doEndProcess();
        } catch (RuntimeException e) {
            if (isTryCatchFinally(current)) {
                getTryCatchFinally(current).doCatchProcess(e);
            } else {
                throw e;
            }
        } finally {
            SpecificationUtil.endScope();
            if (isTryCatchFinally(current)) {
                getTryCatchFinally(current).doFinallyProcess();
            }
            setCurrentProcessor(current);
            SpecificationUtil.execEvent(engine, QM_AFTER_RENDER_PROCESSOR);
            setCurrentProcessor(null);
        }
        return ret;
    }

    /**
     * ボディのレンダリング中に以後のレンダリングを中止する例外。
     * @author Koji Suga (Gluegent Inc.)
     */
    public static class SkipPageException extends Exception {
        private static final long serialVersionUID = -2166811321531507794L;
        public SkipPageException() {
            super();
        }
    }

    /**
     * プロセッサのボディをレンダリングする処理。
     *
     * @param topLevelPage レンダリングの起点となるページ
     * @param current 現在のプロセッサ
     * @param oldBuffered メソッド開始時のバッファするか否かフラグ
     * @return バッファするか否か
     * @throws SkipPageException
     */
    public static boolean renderTemplateProcessorChildren(
            Page topLevelPage, TemplateProcessor current,
            boolean oldBuffered)
            throws SkipPageException {
        ServiceCycle cycle = CycleUtil.getServiceCycle();
        ProcessStatus afterRet;
        boolean buffered = oldBuffered;
        do {
            for (int i = 0; i < current.getChildProcessorSize(); i++) {
                ProcessorTreeWalker child = current.getChildProcessor(i);
                if (child instanceof TemplateProcessor) {
                    TemplateProcessor childProc = (TemplateProcessor) child;
                    final ProcessStatus childRet =
                        renderTemplateProcessor(topLevelPage, childProc);
                    if (childRet == SKIP_PAGE) {
                        throw new SkipPageException();
                    }
                } else {
                    throw new IllegalStateException("child processor type error");
                }
            }
            afterRet = SKIP_BODY;
            saveToCycle(current);
            if (isIteration(current)) {
                if (buffered) {
                    buffered = false;
                    cycle.getResponse().popWriter();
                }
                afterRet = getIteration(current).doAfterChildProcess();
                ProcessorTreeWalker parent = current.getParentProcessor();
                if (parent instanceof TemplateProcessor) {
                    TemplateProcessor parentProc = (TemplateProcessor) parent;
                    if (afterRet == EVAL_BODY_AGAIN && isDuplicated(parentProc)) {
                        saveToCycle(parentProc);
                        parentProc.doEndProcess();
                        parentProc.doStartProcess(null);

                        if (oldBuffered) {
                            buffered = true;
                            ChildEvaluationProcessor processor = getEvaluation(current);
                            processor.setBodyContent(cycle.getResponse().pushNoFlushWriter());
                            processor.doInitChildProcess();
                        }
                    }
                }
            }
        } while (afterRet == EVAL_BODY_AGAIN);
        return buffered;
    }

    private static final String CURRENT_PROCESSOR_KEY = "__currentProcessor__";

    protected static TemplateProcessor getCurrentProcessor() {
        return (TemplateProcessor)CycleUtil.getRequestScope().getAttribute(CURRENT_PROCESSOR_KEY);
    }

    protected static void setCurrentProcessor(TemplateProcessor processor) {
        CycleUtil.getRequestScope().setAttribute(CURRENT_PROCESSOR_KEY, processor);
    }

    // Rendering entry point
    public static ProcessStatus renderProcessorTree(
            Page topLevelPage, ProcessorTreeWalker root) {
        for (int i = 0; i < root.getChildProcessorSize(); i++) {
            ProcessorTreeWalker child = root.getChildProcessor(i);
            if (child instanceof TemplateProcessor) {
                TemplateProcessor childProc = (TemplateProcessor) child;
                final ProcessStatus childRet =
                    renderTemplateProcessor(topLevelPage, childProc);
                if (childRet == SKIP_PAGE) {
                    return SKIP_PAGE;
                }
            } else {
                throw new IllegalStateException("child processor is not a templateProcessor");
            }
        }
        return EVAL_PAGE;
    }

    public static void saveToCycle(Page page) {
        ServiceCycle cycle = CycleUtil.getServiceCycle();
        cycle.setOriginalNode(page);
        cycle.setInjectedNode(page);
    }

    protected static Template getTemplate(String requestedSuffix,
            Page page, String suffix, String extension) {
        if (EngineUtil.getMayaaExtensionName().equals(extension)) {
            SourceDescriptor source = page.getSource();
            if (source.exists() == false) {
                String pageName = page.getPageName();
                throw new PageNotFoundException(pageName, extension);
            }
        } else {
            if (StringUtil.isEmpty(suffix)) {
                if (StringUtil.isEmpty(requestedSuffix)) {
                    CompiledScript script = page.getSuffixScript();
                    suffix = (String) script.execute(null);
                } else {
                    suffix = requestedSuffix;
                }
            }
            return page.getTemplate(suffix, extension);
        }
        return null;
    }

    /**
     * ページをレンダリングする。
     *
     * @param fireEvent beforeRender等のイベントを実行するかどうか。
     *                   layoutのinsertのように実行しない場合はfalseを渡すこと。
     * @param renderer レンダリングを実行するオブジェクト
     * @param variables beforeRender時に変数として宣言するもの
     * @param topLevelPage リクエストのあったページ
     * @param requestedSuffix リクエストされたsuffix
     * @param extension リクエストのあった拡張子
     * @return 後の処理を表すステータス
     */
    public static ProcessStatus renderPage(boolean fireEvent,
            TemplateRenderer renderer, Map variables,
            Page topLevelPage, String requestedSuffix, String extension) {
        if (renderer == null || topLevelPage == null) {
            throw new IllegalArgumentException();
        }
        Page page = topLevelPage;
        String suffix = null;
        saveToCycle(page);
        List pageStack = new LinkedList();
        List templateStack = new LinkedList();
        do {
            // stack for afterRender event.
            pageStack.add(0, page);
            if (fireEvent) {
                SpecificationUtil.startScope(variables);
                SpecificationUtil.execEvent(page, QM_BEFORE_RENDER);
            }
            Template template =
                getTemplate(requestedSuffix, page, suffix, extension);
            if (template != null) {
                // LIFO access
                templateStack.add(0, template);
            }
            suffix = page.getSuperSuffix();
            extension = page.getSuperExtension();
            page = page.getSuperPage();
            variables = null;
        } while(page != null);
        ProcessStatus ret = null;
        if (templateStack.size() > 0) {
            Template[] templates = (Template[])
                    templateStack.toArray(new Template[templateStack.size()]);
            ret = renderer.renderTemplate(topLevelPage, templates);
            saveToCycle(page);
        }
        if (fireEvent) {
            for (int i = 0; i < pageStack.size(); i++) {
                page = (Page) pageStack.get(i);
                saveToCycle(page);
                SpecificationUtil.execEvent(page, QM_AFTER_RENDER);
                SpecificationUtil.endScope();
            }
        }
        return ret;
    }

}
