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

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.seasar.mayaa.builder.injection.InjectionChain;
import org.seasar.mayaa.builder.injection.InjectionResolver;
import org.seasar.mayaa.engine.specification.CopyToFilter;
import org.seasar.mayaa.engine.specification.NodeAttribute;
import org.seasar.mayaa.engine.specification.NodeObject;
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.impl.CONST_IMPL;
import org.seasar.mayaa.impl.ParameterAwareImpl;
import org.seasar.mayaa.impl.engine.EngineUtil;
import org.seasar.mayaa.impl.engine.specification.SpecificationUtil;
import org.seasar.mayaa.impl.util.ObjectUtil;
import org.seasar.mayaa.impl.util.StringUtil;

/**
 * @author Masataka Kurihara (Gluegent, Inc.)
 */
public class EqualsIDInjectionResolver extends ParameterAwareImpl
        implements InjectionResolver, CONST_IMPL {

    private static final long serialVersionUID = 3248707016394881768L;
    private static final Log LOG =
        LogFactory.getLog(EqualsIDInjectionResolver.class);

    private static final CopyToFilter _idFilter = new CheckIDCopyToFilter();
    private List _additionalIds = new ArrayList();
    private boolean _reportResolvedID = true;
    private boolean _reportDuplicatedID = true;

    public EqualsIDInjectionResolver() {
        _additionalIds.add(QM_ID);
    }

    protected CopyToFilter getCopyToFilter() {
        return _idFilter;
    }

    protected boolean isReportResolvedID() {
        return _reportResolvedID;
    }

    protected boolean isReportDuplicatedID() {
        return _reportDuplicatedID;
    }

    protected NodeAttribute getAttribute(SpecificationNode node) {
        for (Iterator it = _additionalIds.iterator(); it.hasNext();) {
            NodeAttribute attr = node.getAttribute((QName) it.next());
            if (attr != null) {
                return attr;
            }
        }
        return null;
    }

    protected String getID(SpecificationNode node) {
        if (node == null) {
            throw new IllegalArgumentException();
        }
        NodeAttribute attr = getAttribute(node);
        if (attr != null) {
            return attr.getValue();
        }
        return null;
    }

    protected void getEqualsIDNodes(
            SpecificationNode node, String id, List specificationNodes) {
        if (node == null || StringUtil.isEmpty(id)) {
            throw new IllegalArgumentException();
        }
        for (Iterator it = node.iterateChildNode(); it.hasNext();) {
            SpecificationNode child = (SpecificationNode) it.next();
            if (id.equals(SpecificationUtil.getAttributeValue(child, QM_ID))) {
                if (QM_MAYAA.equals(node.getQName())) {
                    specificationNodes.add(child);
                } else {
                    // m:mayaa直下でなければ警告し、利用しない
                    logWarnning(id, child, 3);
                }
            }
            getEqualsIDNodes(child, id, specificationNodes);
        }
    }

    public SpecificationNode getNode(
            SpecificationNode original, InjectionChain chain) {
        if (original == null || chain == null) {
            throw new IllegalArgumentException();
        }
        String id = getID(original);
        if (StringUtil.hasValue(id)) {
            Specification spec = SpecificationUtil.findSpecification(original);
            SpecificationNode injected = null;
            while (spec != null) {
                SpecificationNode mayaa = SpecificationUtil.getMayaaNode(spec);
                if (mayaa != null) {
                    List injectNodes = new ArrayList();
                    getEqualsIDNodes(mayaa, id, injectNodes);
                    if (injectNodes.size() > 0) {
                        injected = (SpecificationNode) injectNodes.get(0);
                        if (isReportDuplicatedID() && injectNodes.size() > 1) {
                            logWarnning(id, original, 2);
                        }
                        break;
                    }
                }
                spec = EngineUtil.getParentSpecification(spec);
            }
            if (injected != null) {
                if (QM_IGNORE.equals(injected.getQName())) {
                    return chain.getNode(original);
                }
                return injected.copyTo(getCopyToFilter());
            }
            if (isReportResolvedID()) {
                logWarnning(id, original, 1);
            }
        }
        return chain.getNode(original);
    }

    protected void logWarnning(String id, SpecificationNode node, int number) {
        if (LOG.isWarnEnabled()) {
            String systemID = node.getSystemID();
            String lineNumber = Integer.toString(node.getLineNumber());
            String msg = StringUtil.getMessage(
                    EqualsIDInjectionResolver.class, number,
                    id, systemID, lineNumber);
            LOG.warn(msg);
        }
    }

    // Parameterizable implements ------------------------------------

    public void setParameter(String name, String value) {
        if ("reportUnresolvedID".equals(name)) {
            _reportResolvedID = ObjectUtil.booleanValue(value, true);
        }
        if ("reportDuplicatedID".equals(name)) {
            _reportDuplicatedID = ObjectUtil.booleanValue(value, true);
        }
        if ("addAttribute".equals(name)) {
            _additionalIds.add(SpecificationUtil.parseQName(value));
        }
        super.setParameter(name, value);
    }

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

    protected static class CheckIDCopyToFilter implements CopyToFilter {

        public boolean accept(NodeObject test) {
            if (test instanceof NodeAttribute) {
                NodeAttribute attr = (NodeAttribute) test;
                return attr.getQName().equals(QM_ID) == false;
            }
            return true;
        }

    }

}
