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

import java.io.Serializable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.mayaa.engine.specification.CopyToFilter;
import org.seasar.mayaa.engine.specification.Namespace;
import org.seasar.mayaa.engine.specification.NodeAttribute;
import org.seasar.mayaa.engine.specification.NodeObject;
import org.seasar.mayaa.engine.specification.NodeTreeWalker;
import org.seasar.mayaa.engine.specification.PrefixMapping;
import org.seasar.mayaa.engine.specification.QName;
import org.seasar.mayaa.engine.specification.Specification;
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.impl.CONST_IMPL;
import org.seasar.mayaa.impl.util.StringUtil;
import org.seasar.mayaa.impl.util.collection.NullIterator;

/**
 * @author Masataka Kurihara (Gluegent, Inc.)
 */
public class SpecificationNodeImpl extends NamespaceImpl
        implements SpecificationNode, CONST_IMPL {

    private static final long serialVersionUID = 7084397580802643259L;
    private static final Log LOG = LogFactory.getLog(SpecificationNodeImpl.class);
    private static final CopyToFilter FILTER_ALL = new AllCopyToFilter();

    private int _sequenceID = -1;
    private int _hashCode = 0;
    private Map/*<qName, NodeAttribute>*/ _attributes;
    private NodeTreeWalkerImpl _delegateNodeTreeWalker;
    // for PrefixAwareName
    private QName _qName;
    private transient String _finalizeLabel;

    public SpecificationNodeImpl(QName qName) {
        _qName = qName;
    }

    protected NodeTreeWalker getNodeTreeWalker() {
        if (_delegateNodeTreeWalker == null) {
            synchronized (this) {
                _delegateNodeTreeWalker = new NodeTreeWalkerImpl();
                _delegateNodeTreeWalker.setOwner(this);
            }
        }
        return _delegateNodeTreeWalker;
    }

    protected String makeReleasedLabel() {
        if (_finalizeLabel == null) {
            try {
                _finalizeLabel = toString();
            } catch(Throwable e) {
                _finalizeLabel = getNodeTreeWalker().getSystemID()
                            + " <" + _qName + ">";
            }
        }
        if (_finalizeLabel != null) {
            return "node " + _finalizeLabel + "@" + hashCode() + " unloaded.";
        }
        return "";
    }

    public String toString() {
        StringBuffer path = new StringBuffer();

        if (getParentNode() != null
                && getParentNode() instanceof Specification == false) {
            path.append(getParentNode());
        }

        path.append("/");
        path.append(PrefixAwareNameImpl.forPrefixAwareNameString(
                getQName(), getPrefix()));
        return path.toString();
    }

    protected void finalize() throws Throwable {
        if (LOG.isTraceEnabled()) {
            LOG.trace(makeReleasedLabel());
        }
        super.finalize();
    }

    // PrefixAwareName implements

    public QName getQName() {
        return _qName;
    }

    public String getPrefix() {
        URI namespaceURI = getQName().getNamespaceURI();
        for (Iterator it = iteratePrefixMapping(true); it.hasNext();) {
            PrefixMapping mapping = (PrefixMapping) it.next();
            if (namespaceURI.equals(mapping.getNamespaceURI())) {
                return mapping.getPrefix();
            }
        }
        return "";
    }

    // SpecificaitonNode implements ------------------------------------

    public void setSequenceID(int sequenceID) {
        if (sequenceID < 0) {
            throw new IllegalArgumentException("sequenceID");
        }
        _sequenceID = sequenceID;
        _hashCode = (_qName.toString() + ":" + getSystemID() +
                ":" + _sequenceID + ":" + super.hashCode()).hashCode();
    }

    public int getSequenceID() {
        return _sequenceID;
    }

    public void addAttribute(QName qName, String value) {
        addAttribute(qName, null, value);
    }

    public void addAttribute(QName qName, String originalName, String value) {
        if (qName == null || value == null) {
            throw new IllegalArgumentException();
        }
        synchronized (this) {
            if (_attributes == null) {
                _attributes = new LinkedHashMap();
            }
        }
        synchronized (_attributes) {
            if (_attributes.containsKey(qName) == false) {
                String prefix = null;
                if (originalName != null) {
                    prefix = StringUtil.parsePrefix(originalName);
                }
                NodeAttributeImpl attr = new NodeAttributeImpl(
                        qName, value, prefix);
                _attributes.put(qName, attr);
                attr.setNode(this);
            }
        }
    }

    public NodeAttribute getAttribute(QName qName) {
        if (qName == null) {
            throw new IllegalArgumentException();
        }
        if (_attributes == null) {
            return null;
        }
        return (NodeAttribute) _attributes.get(qName);
    }

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

    public NodeAttribute removeAttribute(QName qName) {
        if (_attributes != null) {
            synchronized (_attributes) {
                return (NodeAttribute) _attributes.remove(qName);
            }
        }
        return null;
    }

    public void clearAttributes() {
        if (_attributes != null) {
            synchronized (_attributes) {
                _attributes.clear();
            }
        }
    }

    public SpecificationNode copyTo(CopyToFilter filter) {
        SpecificationNode copy = SpecificationUtil.createSpecificationNode(
                getQName(), getSystemID(), getLineNumber(), isOnTemplate(), getSequenceID());
        for (Iterator it = iterateAttribute(); it.hasNext();) {
            NodeAttribute attr = (NodeAttribute) it.next();
            if (filter.accept(attr)) {
                copy.addAttribute(attr.getQName(), attr.getValue());
            }
        }
        for (Iterator it = iterateChildNode(); it.hasNext();) {
            SpecificationNode node = (SpecificationNode) it.next();
            if (filter.accept(node)) {
                copy.addChildNode(node.copyTo(filter));
            }
        }
        for (Iterator it = iteratePrefixMapping(false); it.hasNext();) {
            PrefixMapping prefixMapping = (PrefixMapping)it.next();
            copy.addPrefixMapping(
                    prefixMapping.getPrefix(), prefixMapping.getNamespaceURI());
        }
        copy.setParentSpace(SpecificationUtil.getFixedNamespace(getParentSpace()));
        copy.setDefaultNamespaceURI(getDefaultNamespaceURI());
        return copy;
    }

    public SpecificationNode copyTo() {
        return copyTo(FILTER_ALL);
    }

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

    // NodeTreeWalker implemetns ------------------------------------

    public void setParentNode(NodeTreeWalker parent) {
        getNodeTreeWalker().setParentNode(parent);
    }

    public NodeTreeWalker getParentNode() {
        return getNodeTreeWalker().getParentNode();
    }

    public void addChildNode(NodeTreeWalker childNode) {
        getNodeTreeWalker().addChildNode(childNode);
    }

    public void insertChildNode(int index, NodeTreeWalker childNode) {
        getNodeTreeWalker().insertChildNode(index, childNode);
    }

    public Iterator iterateChildNode() {
        return getNodeTreeWalker().iterateChildNode();
    }

    public boolean removeChildNode(NodeTreeWalker node) {
        return getNodeTreeWalker().removeChildNode(node);
    }

    public NodeTreeWalker getChildNode(int index) {
        return getNodeTreeWalker().getChildNode(index);
    }

    public int getChildNodeSize() {
        return getNodeTreeWalker().getChildNodeSize();
    }

    public void clearChildNodes() {
        if (_delegateNodeTreeWalker != null) {
            _delegateNodeTreeWalker.clearChildNodes();
        }
    }

    // PositionAware implements -------------------------------------

    public void setSystemID(String systemID) {
        getNodeTreeWalker().setSystemID(systemID);
    }

    public String getSystemID() {
        return getNodeTreeWalker().getSystemID();
    }

    public void setLineNumber(int lineNumber) {
        getNodeTreeWalker().setLineNumber(lineNumber);
    }

    public int getLineNumber() {
        return getNodeTreeWalker().getLineNumber();
    }

    public void setOnTemplate(boolean onTemplate) {
        getNodeTreeWalker().setOnTemplate(onTemplate);
    }

    public boolean isOnTemplate() {
        return getNodeTreeWalker().isOnTemplate();
    }

    // NodeReferenceResolverFinder implements ------------------------------------

    public NodeReferenceResolver findNodeResolver() {
        return getNodeTreeWalker().findNodeResolver();
    }

    // for serialize

    private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
        out.defaultWriteObject();
        // namespace
        out.writeUTF(super.toString());
        if (getParentSpace() != null
                && getParentSpace().getClass() == NamespaceImpl.class) {
            out.writeObject(getParentSpace());
        } else {
            out.writeObject(new Serializable() {
                private static final long serialVersionUID = 1L;
            });
        }
    }

    private void readObject(java.io.ObjectInputStream in)
            throws java.io.IOException, ClassNotFoundException {
        in.defaultReadObject();
        if (_delegateNodeTreeWalker != null) {
            _delegateNodeTreeWalker.setOwner(this);
        }
        // namespace
        String currentMappingInfo = in.readUTF();
        NamespaceImpl namespace = deserialize(currentMappingInfo);
        setDefaultNamespaceMapping(namespace.getDefaultNamespaceMapping());
        for (Iterator it = namespace.iteratePrefixMapping(false)
                ; it.hasNext(); ) {
            PrefixMapping mapping = (PrefixMapping) it.next();
            addPrefixMapping(mapping.getPrefix(), mapping.getNamespaceURI());
        }
        // parent namespace
        Object nscheck = in.readObject();
        if (nscheck instanceof NamespaceImpl) {
            setParentSpace((Namespace)nscheck);
        }
        findNodeResolver().nodeLoaded(this);
    }

    private Object readResolve() {
        for (Iterator it = iterateChildNode(); it.hasNext(); ) {
            NodeTreeWalker child = (NodeTreeWalker) it.next();
            child.setParentNode(this);
            if (child instanceof Namespace) {
                ((Namespace)child).setParentSpace(this);
            }
        }
        for (Iterator it =  iterateAttribute(); it.hasNext(); ) {
            NodeAttributeImpl attr = (NodeAttributeImpl) it.next();
            attr.setNode(this);
        }
        return this;
    }

    public boolean equals(Object other) {
        if (other instanceof SpecificationNodeImpl) {
            SpecificationNodeImpl otherNode = (SpecificationNodeImpl) other;
            return _hashCode == otherNode._hashCode;
        }
        return false;
    }

    public int hashCode() {
        return _hashCode;
    }

    // support class --------------------------------------------------

    protected static class AllCopyToFilter implements CopyToFilter {

        public boolean accept(NodeObject test) {
            return true;
        }

    }

}
