/*
 * Copyright 2004-2009 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.Iterator;
import java.util.Map;

import org.apache.commons.collections.map.AbstractReferenceMap;
import org.apache.commons.collections.map.ReferenceMap;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
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.processor.ProcessStatus;
import org.seasar.mayaa.engine.processor.TemplateProcessor;
import org.seasar.mayaa.impl.CONST_IMPL;
import org.seasar.mayaa.impl.cycle.CycleUtil;
import org.seasar.mayaa.impl.cycle.script.ScriptUtil;
import org.seasar.mayaa.impl.engine.specification.SpecificationImpl;
import org.seasar.mayaa.impl.engine.specification.SpecificationUtil;
import org.seasar.mayaa.impl.provider.ProviderUtil;
import org.seasar.mayaa.impl.util.StringUtil;

/**
 * @author Masataka Kurihara (Gluegent, Inc)
 */
public class PageImpl extends SpecificationImpl implements Page, CONST_IMPL {

    private static final long serialVersionUID = -7151911061582725013L;
    static final Log LOG = LogFactory.getLog(PageImpl.class);
    private static final String CURRENT_PAGE_KEY = "__currentPage__";
    private static final String CURRENT_COMPONENT_KEY = "__currentComponent__";

    private String _pageName;
    private transient Page _superPage;
    private transient String _superSuffix;
    private transient String _superExtension;
    private transient Map _beginRenderListeners;
    private String _suffixScriptText;
    private CompiledScript _suffixScript;

    public void initialize(String pageName) {
        if (StringUtil.isEmpty(pageName)) {
            throw new IllegalArgumentException();
        }
        if (pageName.charAt(0) != '/') {
            pageName = "/" + pageName;
        }
        _pageName = pageName;
    }

    protected void prepareSuper() {
        if (_superPage != null && _superPage.isDeprecated() == false) {
            return;
        }
        synchronized (this) {
            if (_superPage != null && _superPage.isDeprecated() == false) {
                return;
            }
            // TODO mayaa以外からも取得できるようにする
            String extendsPath =
                SpecificationUtil.getMayaaAttributeValue(this, QM_EXTENDS);
            if (StringUtil.isEmpty(extendsPath)) {
                return;
            }
            Engine engine = ProviderUtil.getEngine();
            String suffixSeparator = engine.getParameter(SUFFIX_SEPARATOR);
            String[] pagePath = StringUtil.parsePath(extendsPath, suffixSeparator);

            _superPage = engine.getPage(
                    StringUtil.adjustRelativePath(_pageName, pagePath[0]));
            _superSuffix = pagePath[1];
            _superExtension = pagePath[2];
        }
    }

    public Page getSuperPage() {
        prepareSuper();
        return _superPage;
    }

    public String getSuperSuffix() {
        prepareSuper();
        return _superSuffix;
    }

    public String getSuperExtension() {
        prepareSuper();
        return _superExtension;
    }

    public String getPageName() {
        return _pageName;
    }

    public CompiledScript getSuffixScript() {
        String value = SpecificationUtil.getMayaaAttributeValue(
                this, QM_TEMPLATE_SUFFIX);
        if (StringUtil.isEmpty(value)) {
            value = SpecificationUtil.getMayaaAttributeValue(
                    ProviderUtil.getEngine(), QM_TEMPLATE_SUFFIX);
        }
        if (StringUtil.isEmpty(value)) {
            value = "";
        }
        if (_suffixScript != null && value.equals(_suffixScriptText)) {
            return _suffixScript;
        }
        _suffixScript = ScriptUtil.compile(value, String.class);
        _suffixScriptText = value;
        return _suffixScript;
    }

    protected Template findTemplateFromCache(String systemID) {
        Engine engine = ProviderUtil.getEngine();
        return (Template) engine.findSpecificationFromCache(systemID);
    }

    protected Template getTemplateFromFixedSuffix(
            String suffix, String extension) {
        if (suffix == null || extension == null) {
            throw new IllegalArgumentException();
        }
        Engine engine = ProviderUtil.getEngine();
        synchronized (engine) {
            String systemID = engine.getTemplateID(this, suffix, extension);
            Template template = findTemplateFromCache(systemID);
            if (template != null) {
                if (template.getSource().exists()) {
                    return template;
                }
                return null;
            }
            return engine.createTemplateInstance(this, suffix, extension);
        }
    }

    public Template getTemplate(String suffix, String extension) {
        if (suffix == null) {
            suffix = "";
        }
        if (extension == null) {
            extension = "";
        }
        if (EngineUtil.getMayaaExtensionName().equals(extension)) {
            return null;
        }
        Template template = getTemplateFromFixedSuffix(suffix, extension);
        if (template == null && StringUtil.hasValue(suffix)) {
            template = getTemplateFromFixedSuffix("", extension);
        }
        if (template == null) {
            throw new PageNotFoundException(_pageName, extension);
        }
        return template;
    }

    public ProcessStatus doPageRender(
            String requestedSuffix, String extension) {
        Page prevPage = getCurrentPage();
        setCurrentPage(this);
        Engine engine = ProviderUtil.getEngine();
        SpecificationUtil.execEvent(engine, QM_BEFORE_RENDER_PAGE);
        try {
            Page component = this;
            Page superPage = component.getSuperPage();
            if (superPage != null) {
                component = superPage;
            }
            Page prevComponent = getCurrentComponent();
            setCurrentComponent(component);
            SpecificationUtil.execEvent(engine, QM_BEFORE_RENDER_COMPONENT);
            try {
                notifyBeginRender();
                return RenderUtil.renderPage(true, this, null,
                        this, requestedSuffix, extension);
            } finally {
                setCurrentComponent(component);
                try {
                    SpecificationUtil.execEvent(engine, QM_AFTER_RENDER_COMPONENT);
                } finally {
                    setCurrentComponent(prevComponent);
                }
            }
        } finally {
            setCurrentPage(this);
            try {
                SpecificationUtil.execEvent(engine, QM_AFTER_RENDER_PAGE);
            } finally {
                setCurrentPage(prevPage);
            }
        }
    }

    protected static Page getCurrentPage() {
        return (Page)CycleUtil.getRequestScope().getAttribute(CURRENT_PAGE_KEY);
    }

    protected static void setCurrentPage(Page page) {
        CycleUtil.getRequestScope().setAttribute(CURRENT_PAGE_KEY, page);
    }


    public static Page getCurrentComponent() {
        return (Page)CycleUtil.getRequestScope().getAttribute(CURRENT_COMPONENT_KEY);
    }

    public static void setCurrentComponent(Page component) {
        CycleUtil.getRequestScope().setAttribute(CURRENT_COMPONENT_KEY, component);
    }

    // for serialize

    private void readObject(java.io.ObjectInputStream in)
            throws java.io.IOException, ClassNotFoundException {
        in.defaultReadObject();
    }

    // TemplateRenderer implements ----------------------------------

    public ProcessStatus renderTemplate(
            Page topLevelPage, Template[] templates) {
        if (topLevelPage == null || templates == null
                || templates.length == 0) {
            throw new IllegalArgumentException();
        }
        return templates[0].doTemplateRender(topLevelPage);
    }

    protected Map getBeginRenderListeners() {
        synchronized(this) {
            if (_beginRenderListeners == null) {
                _beginRenderListeners = new ReferenceMap(
                        AbstractReferenceMap.SOFT, AbstractReferenceMap.WEAK, true);
            }
        }
        return _beginRenderListeners;
    }

    public boolean registBeginRenderNotifier(TemplateProcessor processor) {
        boolean result =
            getBeginRenderListeners().containsKey(processor) == false;
        if (result == false) {
            getBeginRenderListeners().put(processor, Boolean.TRUE);
        }
        return result;
    }

    protected void notifyBeginRender() {
        for (Iterator it = getBeginRenderListeners().keySet().iterator();
                it.hasNext(); ) {
            TemplateProcessor listener = (TemplateProcessor) it.next();
            listener.notifyBeginRender(this);
        }
    }

}
