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

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.mayaa.builder.SequenceIDGenerator;
import org.seasar.mayaa.builder.library.LibraryManager;
import org.seasar.mayaa.builder.library.ProcessorDefinition;
import org.seasar.mayaa.engine.Page;
import org.seasar.mayaa.engine.processor.OptimizableProcessor;
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.specification.Namespace;
import org.seasar.mayaa.engine.specification.NodeTreeWalker;
import org.seasar.mayaa.engine.specification.QName;
import org.seasar.mayaa.engine.specification.SpecificationNode;
import org.seasar.mayaa.engine.specification.URI;
import org.seasar.mayaa.engine.specification.serialize.NodeReferenceResolver;
import org.seasar.mayaa.engine.specification.serialize.NodeResolveListener;
import org.seasar.mayaa.engine.specification.serialize.ProcessorReferenceResolver;
import org.seasar.mayaa.impl.CONST_IMPL;
import org.seasar.mayaa.impl.engine.TemplateImpl;
import org.seasar.mayaa.impl.engine.specification.QNameImpl;
import org.seasar.mayaa.impl.engine.specification.SpecificationImpl;
import org.seasar.mayaa.impl.engine.specification.URIImpl;
import org.seasar.mayaa.impl.engine.specification.serialize.NodeSerializeController;
import org.seasar.mayaa.impl.provider.ProviderUtil;
import org.seasar.mayaa.impl.util.ObjectUtil;

/**
 * @author Masataka Kurihara (Gluegent, Inc.)
 */
public class TemplateProcessorSupport
        implements TemplateProcessor, OptimizableProcessor {

    private static final long serialVersionUID = 8297043573746398769L;
    private static final Log LOG = LogFactory.getLog(TemplateProcessorSupport.class);
    private static final String PREFIX_UNIQUE_ID = "_m";

    private transient ProcessorTreeWalker _parent;
    // _childrenはビルド時のみ変更される。そのときはEngineのインスタンスでガードされる。
    private List/*<ProcessorTreeWalker>*/ _children;
    protected SpecificationNode _originalNode;
    protected transient SpecificationNode _injectedNode;
    private boolean _evalBodyInclude = true;
    private transient ProcessorDefinition _definition;


    public void initialize() {
        // no operation
    }

    public String getUniqueID() {
        int sequenceID = getOriginalNode().getSequenceID();
        return PREFIX_UNIQUE_ID + sequenceID;
    }

    // MLD property
    public void setEvalBodyInclude(boolean evalBodyInclude) {
        _evalBodyInclude = evalBodyInclude;
    }

    public boolean isEvalBodyInclude() {
        return _evalBodyInclude;
    }

    public ProcessStatus doStartProcess(Page topLevelPage) {
        if (_evalBodyInclude) {
            return ProcessStatus.EVAL_BODY_INCLUDE;
        }
        return ProcessStatus.SKIP_BODY;
    }

    public ProcessStatus doEndProcess() {
        return ProcessStatus.EVAL_PAGE;
    }

    public void setOriginalNode(SpecificationNode node) {
        if (node == null) {
            throw new IllegalArgumentException("originalNode must not be null");
        }
        _originalNode = node;
    }

    public SpecificationNode getOriginalNode() {
        if (_originalNode == null) {
            throw new IllegalStateException("originalNode is null");
        }
        return _originalNode;
    }

    public void setInjectedNode(SpecificationNode node) {
        if (node == null) {
            throw new IllegalArgumentException("injectedNode must not be null");
        }
        _injectedNode = node;
    }

    public SpecificationNode getInjectedNode() {
        if (_injectedNode == null) {
            throw new IllegalStateException("injectedNode is null");
        }
        return _injectedNode;
    }

    public void setProcessorDefinition(ProcessorDefinition definition) {
        if (definition == null) {
            throw new IllegalArgumentException("processorDefinition must not be null");
        }
        _definition = definition;
    }

    public ProcessorDefinition getProcessorDefinition() {
        if (_definition == null) {
            throw new IllegalStateException("processorDefinition is null");
        }
        return _definition;
    }

    protected boolean isHTML(QName qName) {
        URI namespaceURI = qName.getNamespaceURI();
        return CONST_IMPL.URI_HTML.equals(namespaceURI);
    }

    protected boolean isXHTML(QName qName) {
        URI namespaceURI = qName.getNamespaceURI();
        return CONST_IMPL.URI_XHTML.equals(namespaceURI);
    }

    // ProcessorTreeWalker implements --------------------------------

    public Map getVariables() {
        return null;
    }

    public void setParentProcessor(ProcessorTreeWalker parent) {
        if (parent == null) {
            throw new IllegalArgumentException("parentProcessor must not be null");
        }
        _parent = parent;
    }

    public ProcessorTreeWalker getParentProcessor() {
        return _parent;
    }

    public void addChildProcessor(ProcessorTreeWalker child) {
        insertProcessor(Integer.MAX_VALUE, child);
    }

    public synchronized void insertProcessor(int index, ProcessorTreeWalker child) {
        if (child == null) {
            throw new IllegalArgumentException("child is null");
        }
        if (_children == null) {
            _children = new ArrayList();
        }
        if (index == Integer.MAX_VALUE) {
            index = _children.size();
        }
        _children.add(index, child);
        child.setParentProcessor(this);
        for (index += 1; index < _children.size(); index++) {
            child = (ProcessorTreeWalker)_children.get(index);
            child.setParentProcessor(this);
        }
    }

    public int getChildProcessorSize() {
        if (_children == null) {
            return 0;
        }
        return _children.size();
    }

    public ProcessorTreeWalker getChildProcessor(int index) {
        if (index < 0 || index >= getChildProcessorSize()) {
            throw new IndexOutOfBoundsException();
        }
        return (ProcessorTreeWalker) _children.get(index);
    }

    public synchronized boolean removeProcessor(ProcessorTreeWalker child) {
        if (_children != null) {
            return _children.remove(child);
        }
        return false;
    }

    public synchronized void clearChildProcessors() {
        if (_children != null) {
            try {
                _children.clear();
            } finally {
                _children = null;
            }
        }
    }

    public ProcessorTreeWalker[] divide(SequenceIDGenerator sequenceIDGenerator) {
        return new ProcessorTreeWalker[] { this };
    }

    public void notifyBeginRender(Page topLevelPage) {
        // no operation
    }

    protected void finalize() throws Throwable {
        if (LOG.isTraceEnabled()) {
            String name = ObjectUtil.getSimpleClassName(getClass());
            if (_definition != null) {
                LOG.trace(name + " " + getProcessorDefinition().getName() + " unloaded.");
            } else {
                LOG.trace(name + " " + " unloaded.");
            }
        }
        super.finalize();
    }

    public void kill() {
        // TODO deprecated のため削除
    }

    // for serialize
    private static final String UNIQUENESS_MARK = "<<root>>";

    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();
        String originalNodeID = NodeSerializeController.makeKey(_originalNode);
        String uniqueID = getUniqueID();
        out.writeUTF(originalNodeID);
        out.writeUTF(uniqueID);
        out.writeUTF(_injectedNode.getQName().getNamespaceURI().getValue());
        out.writeUTF(_injectedNode.getQName().getLocalName());

        NodeSerializeController controller = SpecificationImpl.nodeSerializer();
        if (controller.collectNode(_injectedNode)) {
        	// オリジナルの保存
        	out.writeUTF(UNIQUENESS_MARK);
        	out.writeObject(_injectedNode);
        } else {
        	// 参照の保存
        	out.writeUTF(NodeSerializeController.makeKey(_injectedNode));
        }
        /*
        if (_injectedNode.getParentNode() != null && !(getParentProcessor() instanceof Template)) {
            String injectedNodeID = NodeSerializeController.makeKey(_injectedNode);
            out.writeUTF(injectedNodeID);
        } else {
            out.writeUTF(DUPLICATE_ROOT_MARK);
            out.writeObject(_injectedNode);
        }
       	*/
    }

    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        in.defaultReadObject();
        String originalNodeID = in.readUTF();
        String processorUniqueID = in.readUTF();
        QName qName = QNameImpl.getInstance(URIImpl.getInstance(in.readUTF()), in.readUTF());
        LibraryManager libraryManager = ProviderUtil.getLibraryManager();
        setProcessorDefinition(libraryManager.getProcessorDefinition(qName));

        String injectedNodeID = in.readUTF();
        NodeResolveListener listener = new TemplateProcessorSupportOriginalNodeListener(this);
        findNodeResolver().registResolveNodeListener(originalNodeID, listener);

        if (UNIQUENESS_MARK.equals(injectedNodeID)) {
            _injectedNode = (SpecificationNode) in.readObject();
        } else {
            listener = new TemplateProcessorSupportInjectedNodeListener(this);
            findNodeResolver().registResolveNodeListener(injectedNodeID, listener);
        }

        ProcessorReferenceResolver resolver = findProcessorResolver();
        resolver.processorLoaded(processorUniqueID, this);

        // readResolveはextendsした最終クラスでしか発生しないので、ここで解決
        if (_children != null) {
            for (int i = _children.size() - 1; i >= 0 ; i--) {
                ProcessorTreeWalker child =
                    (ProcessorTreeWalker) _children.get(i);
                child.setParentProcessor(this);
            }
        }
        LOG.debug("templateProcessorSupport loaded");
    }

    protected void nodeLoadAfter() {
        if (_originalNode != null && _injectedNode != null) {
            Namespace parentSpace = getOriginalNode().getParentSpace();
            if (parentSpace != null
                    && getInjectedNode().getParentSpace() == null) {
                getInjectedNode().setParentSpace(parentSpace);
            }
        }
    }

    private static class TemplateProcessorSupportOriginalNodeListener
            implements NodeResolveListener {

        private TemplateProcessorSupport _target;

        public TemplateProcessorSupportOriginalNodeListener(
                TemplateProcessorSupport target) {
            _target = target;
        }

        public void notify(
                String uniqueID, NodeTreeWalker loadedInstance) {
            if (loadedInstance instanceof SpecificationNode) {
                SpecificationNode specNode =
                    (SpecificationNode)loadedInstance;
                _target._originalNode = specNode;
                _target.nodeLoadAfter();
            }
        }

        public void release() {
            _target = null;
        }
    }

    private static class TemplateProcessorSupportInjectedNodeListener
            implements NodeResolveListener {

        private TemplateProcessorSupport _target;

        public TemplateProcessorSupportInjectedNodeListener(
                TemplateProcessorSupport target) {
            _target = target;
        }

        public void notify(
                String uniqueID, NodeTreeWalker loadedInstance) {
            if (loadedInstance instanceof SpecificationNode) {
                SpecificationNode specNode =
                    (SpecificationNode)loadedInstance;
                _target._injectedNode = specNode;
                _target.nodeLoadAfter();
            }
        }

        public void release() {
            _target = null;
        }
    }

    public ProcessorReferenceResolver findProcessorResolver() {
        return TemplateImpl.processorSerializer();
    }

    public NodeReferenceResolver findNodeResolver() {
        return SpecificationImpl.nodeSerializer();
    }

}
