/*
 * 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.processor;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Stack;

import org.seasar.mayaa.cycle.CycleWriter;
import org.seasar.mayaa.engine.Page;
import org.seasar.mayaa.engine.processor.ChildEvaluationProcessor;
import org.seasar.mayaa.engine.processor.InformalPropertyAcceptable;
import org.seasar.mayaa.engine.processor.ProcessStatus;
import org.seasar.mayaa.engine.processor.ProcessorProperty;
import org.seasar.mayaa.engine.specification.PrefixAwareName;
import org.seasar.mayaa.impl.cycle.CycleUtil;
import org.seasar.mayaa.impl.cycle.DefaultCycleLocalInstantiator;
import org.seasar.mayaa.impl.util.collection.NullIterator;

/**
 * @author Masataka Kurihara (Gluegent, Inc.)
 */
public abstract class AbstractAttributableProcessor
        extends TemplateProcessorSupport
        implements ChildEvaluationProcessor, InformalPropertyAcceptable {

    private static final long serialVersionUID = -1406205460425148574L;
    private boolean _childEvaluation;
    private List/*<Serializable(ProcessorProperty or PrefixAwareName)>*/
                    _attributes;

    private static final String PROCESS_TIME_INFO_KEY =
        AbstractAttributableProcessor.class.getName() + "#processTimeInfo";
    static {
        CycleUtil.registVariableFactory(PROCESS_TIME_INFO_KEY,
                new DefaultCycleLocalInstantiator() {
                    public Object create(Object owner, Object[] params) {
                        return new Stack/*<ProcesstimeInfo>*/();
                    }
                });
    }

    private void clearProcesstimeInfo() {
        CycleUtil.clearLocalVariable(PROCESS_TIME_INFO_KEY, this);
    }

    protected void prepareProcesstimeInfo() {
        Stack piStack = (Stack) CycleUtil.getLocalVariable(
                PROCESS_TIME_INFO_KEY, this, null);
        if (piStack.size() == 0) {
            piStack.push(new ProcesstimeInfo());
        }
    }

    protected ProcesstimeInfo pushProcesstimeInfo() {
        Stack piStack = (Stack) CycleUtil.getLocalVariable(
                PROCESS_TIME_INFO_KEY, this, null);
        return (ProcesstimeInfo) piStack.push(new ProcesstimeInfo());
    }

    protected ProcesstimeInfo peekProcesstimeInfo() {
        return (ProcesstimeInfo) ((Stack) CycleUtil.getLocalVariable(
                PROCESS_TIME_INFO_KEY, this, null)).peek();
    }

    protected ProcesstimeInfo popProcesstimeInfo() {
        Stack piStack = (Stack) CycleUtil.getLocalVariable(
                PROCESS_TIME_INFO_KEY, this, null);
        return (ProcesstimeInfo) piStack.pop();
    }

    // MLD property
    public void setChildEvaluation(boolean childEvaluation) {
        _childEvaluation = childEvaluation;
    }

    // MLD method
    public void addInformalProperty(PrefixAwareName name, Serializable attr) {
        if (_attributes == null) {
            _attributes = new ArrayList();
        }
        _attributes.add(attr);
    }

    /**
     * @deprecated 1.1.9 代わりに{@link #getInformalPropertyClass()}を使う
     */
    public Class getPropertyClass() {
        return getInformalPropertyClass();
    }

    /**
     * @deprecated 1.1.9 代わりに{@link #getInformalPropertyClass()}を使う
     */
    public Class getExpectedClass() {
        return getInformalExpectedClass();
    }

    public Class getInformalPropertyClass() {
        return ProcessorProperty.class;
    }

    public Class getInformalExpectedClass() {
        return Object.class;
    }

    public Iterator iterateInformalProperties() {
        if (_attributes == null) {
            return NullIterator.getInstance();
        }
        return _attributes.iterator();
    }

    // processtime method
    public void addProcesstimeProperty(ProcessorProperty prop) {
        if (prop == null) {
            throw new IllegalArgumentException();
        }
        prepareProcesstimeInfo();
        ProcesstimeInfo info = peekProcesstimeInfo();
        info.addProcesstimeProperty(prop);
    }

    public boolean hasProcesstimeProperty(ProcessorProperty prop) {
        if (prop == null) {
            throw new IllegalArgumentException();
        }
        prepareProcesstimeInfo();
        ProcesstimeInfo info = peekProcesstimeInfo();
        return info.hasProcesstimeProperty(prop);
    }

    public Iterator iterateProcesstimeProperties() {
        prepareProcesstimeInfo();
        ProcesstimeInfo info = peekProcesstimeInfo();
        return info.iterateProcesstimeProperties();
    }

    protected abstract ProcessStatus writeStartElement();

    protected abstract void writeBody(String body);

    protected abstract void writeEndElement();

    /**
     * このメソッドをoverrideしないこと。
     * 代わりに{@link #processStart(Page)}をoverrideすること。
     */
    public ProcessStatus doStartProcess(Page topLevelPage) {
        if (_childEvaluation) {
            prepareProcesstimeInfo();
            pushProcesstimeInfo();
        }
        return processStart(topLevelPage);
    }

    /**
     * 子クラスはdoStartProcessではなくこのメソッドをoverrideすること。
     * ProcesstimeInfoを取得するには{@link #peekProcesstimeInfo()}を使用します。
     *
     * @param topLevelPage 描画トップレベルのページ。
     * @return 子プロセッサを処理する場合にはEVAL_BODY_INCLUDE、
     * 子プロセッサの処理をスキップする場合にはSKIP_BODYを返す。
     * ({@link #writeStartElement()}を呼ぶ)
     */
    protected ProcessStatus processStart(Page topLevelPage) {
        if (_childEvaluation) {
            return ProcessStatus.EVAL_BODY_BUFFERED;
        }
        return writeStartElement();
    }

    public boolean isChildEvaluation() {
        return _childEvaluation;
    }

    public void setBodyContent(CycleWriter body) {
        if (_childEvaluation) {
            if (body == null) {
                throw new IllegalArgumentException();
            }
            ProcesstimeInfo info = peekProcesstimeInfo();
            info.setBody(body);
        }
    }

    public void doInitChildProcess() {
        // do nothing.
    }

    public boolean isIteration() {
        return false;
    }

    public ProcessStatus doAfterChildProcess() {
        return ProcessStatus.SKIP_BODY;
    }

    /**
     * このメソッドをoverrideしないこと。
     * 代わりに{@link #processEnd()}をoverrideすること。
     */
    public ProcessStatus doEndProcess() {
        try {
            return processEnd();
        } finally {
            if (_childEvaluation) {
                popProcesstimeInfo();
            }
        }
    }

    /**
     * 子クラスはdoEndProcessではなくこのメソッドをoverrideすること。
     * ProcesstimeInfoを取得するには{@link #peekProcesstimeInfo()}を使用します。
     *
     * @return ページのこのタグ以降を処理する場合にはEVAL_PAGE、
     * 以降の処理をスキップする場合にはSKIP_PAGE。
     */
    protected ProcessStatus processEnd() {
        if (_childEvaluation) {
            writeStartElement();
            ProcesstimeInfo info = peekProcesstimeInfo();
            CycleWriter body = info.getBody();
            if (body != null) {
                writeBody(body.getString());
            }
        }
        writeEndElement();
        return ProcessStatus.EVAL_PAGE;
    }

    // for serialize

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

    //helper class, methods ----------------------------------------

    protected static class ProcesstimeInfo {

        private CycleWriter _body;
        private List _processtimeProperties;

        public void setBody(CycleWriter body) {
            if (body == null) {
                throw new IllegalArgumentException();
            }
            _body = body;
        }

        public CycleWriter getBody() {
            return _body;
        }

        public boolean hasProcesstimeProperty(ProcessorProperty property) {
            if (property == null) {
                throw new IllegalArgumentException();
            }
            if (_processtimeProperties == null) {
                return false;
            }
            return _processtimeProperties.contains(property);
        }

        public void addProcesstimeProperty(ProcessorProperty property) {
            if (property == null) {
                throw new IllegalArgumentException();
            }
            if (_processtimeProperties == null) {
                _processtimeProperties = new ArrayList();
            }
            if (_processtimeProperties.contains(property) == false) {
                _processtimeProperties.add(property);
            }
        }

        public Iterator iterateProcesstimeProperties() {
            if (_processtimeProperties == null) {
                return NullIterator.getInstance();
            }
            return _processtimeProperties.iterator();
        }

    }

}
