/*
 * 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.util.Iterator;
import java.util.Stack;

import org.seasar.mayaa.cycle.scope.AttributeScope;
import org.seasar.mayaa.engine.Page;
import org.seasar.mayaa.engine.processor.IterationProcessor;
import org.seasar.mayaa.engine.processor.ProcessStatus;
import org.seasar.mayaa.engine.processor.ProcessorProperty;
import org.seasar.mayaa.impl.cycle.CycleUtil;
import org.seasar.mayaa.impl.cycle.DefaultCycleLocalInstantiator;
import org.seasar.mayaa.impl.provider.ProviderUtil;
import org.seasar.mayaa.impl.util.IteratorUtil;

/**
 * @author Koji Suga (Gluegent, Inc.)
 */
public class ForEachProcessor extends TemplateProcessorSupport
        implements IterationProcessor {

    private static final long serialVersionUID = 5143412585484588336L;

    private static final String PROCESS_TIME_INFO_KEY =
            ForEachProcessor.class.getName() + "#processTimeInfo";
    static {
        CycleUtil.registVariableFactory(PROCESS_TIME_INFO_KEY,
                new DefaultCycleLocalInstantiator() {
            public Object create(Object owner, Object[] params) {
                ForEachProcessor processor = (ForEachProcessor) owner;
                return processor.new IndexIteratorStack();
            }
        });
    }

    private String _var;
    protected ProcessorProperty _items;
    protected String _indexName;

    // MLD property, required
    public void setVar(String var) {
        _var = var;
    }

    // MLD property, required=true, expectedClass=void
    public void setItems(ProcessorProperty items) {
        if (items == null) {
            throw new IllegalArgumentException();
        }
        _items = items;
    }

    // MLD property
    public void setIndexName(String indexName) {
        _indexName = indexName;
    }

    public boolean isIteration() {
        return true;
    }

    /**
     * 次の要素があるかどうか判定します。
     * また、次の要素がある場合はそれを現在のスコープのvarにセットします。
     * このとき必要ならばindexもセットします。
     *
     * @return 次の要素があるならtrue
     */
    protected boolean prepareEvalBody() {
        IndexIteratorStack stack = (IndexIteratorStack) CycleUtil.getLocalVariable(
                PROCESS_TIME_INFO_KEY, this, null);
        IndexedIterator iterator = stack.peek();

        if (iterator.hasNext() == false) {
            return false;
        }

        AttributeScope currentScope = CycleUtil.getCurrentPageScope();
        currentScope.setAttribute(_var, iterator.next());
        if (_indexName != null) {
            currentScope.setAttribute(_indexName, iterator.getNextIndex());
        }
        return true;
    }

    public ProcessStatus doStartProcess(Page topLevelPage) {
        if (_items == null || _var == null) {
            throw new IllegalStateException();
        }
        IndexIteratorStack stack = (IndexIteratorStack) CycleUtil.getLocalVariable(
                PROCESS_TIME_INFO_KEY, this, null);
        stack.pushOne();

        if (prepareEvalBody() == false) {
            stack.pop();
            return ProcessStatus.SKIP_BODY;
        }
        return ProcessStatus.EVAL_BODY_INCLUDE;
    }

    public ProcessStatus doAfterChildProcess() {
        if (prepareEvalBody() == false) {
            IndexIteratorStack stack = (IndexIteratorStack) CycleUtil.getLocalVariable(
                    PROCESS_TIME_INFO_KEY, this, null);
            stack.pop();
            return ProcessStatus.SKIP_BODY;
        }
        return ProcessStatus.EVAL_BODY_AGAIN;
    }

    // for serialize

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

    // support class

    /**
     * forEachの入れ子に対応するためのIndexIterator専用Stackクラス。
     *
     * @author Koji Suga (Gluegent Inc.)
     */
    private class IndexIteratorStack {
        private Stack _stack;

        public IndexIteratorStack() {
            _stack = new Stack();
        }

        public void pushOne() {
            _stack.push(new IndexedIterator());
        }

        public IndexedIterator pop() {
            return (IndexedIterator) _stack.pop();
        }

        public IndexedIterator peek() {
            return (IndexedIterator) _stack.peek();
        }
    }

    /**
     * nextの回数を取得できるIterator。
     * getNextIndex() は 0～を返します。
     * ただし余分な処理をしないために、next()の次にgetNextINdex()を呼ぶ
     * 形で使うことを前提とします。
     *
     * @author Koji Suga (Gluegent Inc.)
     */
    private class IndexedIterator {
        private int _index;
        private Iterator _iterator;

        public IndexedIterator() {
            if (_indexName != null) {
                _index = -1;
            }
            Object obj =
                ProviderUtil.getScriptEnvironment().convertFromScriptObject(
                        _items.getValue().execute(null));
            _iterator = IteratorUtil.toIterator(obj);
        }

        public Integer getNextIndex() {
            return new Integer(++_index);
        }

        public boolean hasNext() {
            return _iterator.hasNext();
        }

        public Object next() {
            return _iterator.next();
        }

    }
}
