/*
 * 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.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.Namespace;
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.engine.specification.xpath.XPathUtil;
import org.seasar.mayaa.impl.util.StringUtil;

/**
 * m:xpathに記述されたxpath式にマッチするノードをインジェクション対象とします。
 *
 * JIRA[MAYAA-28] position関数が正常動作しないバグあり。
 *
 * @author Masataka Kurihara (Gluegent, Inc.)
 * @author Koji Suga (Gluegent Inc.)
 */
public class XPathMatchesInjectionResolver extends ParameterAwareImpl
        implements InjectionResolver, CONST_IMPL {

    private static final long serialVersionUID = -3738385077054952571L;
    private static final Log LOG =
        LogFactory.getLog(XPathMatchesInjectionResolver.class);
    protected static final QName QM_XPATH =
        SpecificationUtil.createQName("xpath");

    private static final CopyToFilter _xpathFilter = new CheckXPathCopyToFilter();

    protected CopyToFilter getCopyToFilter() {
        return _xpathFilter;
    }

    /**
     * Mayaaファイル内のm:xpathを持つノードを集め、リストに入れます。
     *
     * @param node Mayaaのノードツリー
     * @param specificationNodes m:xpathを持つノードのリスト
     */
    protected void getXPathNodes(
            SpecificationNode node, List specificationNodes) {
        if (node == null) {
            throw new IllegalArgumentException();
        }
        for (Iterator it = node.iterateChildNode(); it.hasNext();) {
            SpecificationNode child = (SpecificationNode) it.next();
            String xpath = SpecificationUtil.getAttributeValue(child, QM_XPATH);
            if (StringUtil.hasValue(xpath)) {
                if (QM_MAYAA.equals(node.getQName())) {
                    specificationNodes.add(child);
                } else {
                    // m:mayaa直下でなければ警告し、利用しない
                    logWarnning(xpath, child, 1);
                }
            }
            getXPathNodes(child, specificationNodes);
        }
    }

    public SpecificationNode getNode(
            SpecificationNode original, InjectionChain chain) {
        if (original == null || chain == null) {
            throw new IllegalArgumentException();
        }

        // TODO テンプレートのprefix定義、Mayaaファイルのを反映させる
        Namespace namespace = SpecificationUtil.createNamespace();
        namespace.addPrefixMapping("m", URI_MAYAA);

        // mayaaファイル内のm:xpathを持つすべてのノードを対象とする
        Specification spec = SpecificationUtil.findSpecification(original);
        List injectNodes = new ArrayList();
        while (spec != null) {
            SpecificationNode mayaa = SpecificationUtil.getMayaaNode(spec);
            if (mayaa != null) {
                getXPathNodes(mayaa, injectNodes);
            }
            spec = EngineUtil.getParentSpecification(spec);
        }

        for (Iterator it = injectNodes.iterator(); it.hasNext();) {
            SpecificationNode injected = (SpecificationNode) it.next();
            String mayaaPath = SpecificationUtil.getAttributeValue(
                    injected, QM_XPATH);
            // injectedをnamespaceとして渡すとfunctionのデフォルトnamesapceが
            // URI_MAYAAになってしまうため、デフォルトnamespaceのないものを渡す
            if (XPathUtil.matches(original, mayaaPath, namespace)) {
                return injected.copyTo(getCopyToFilter());
            }
        }
        return chain.getNode(original);
    }

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

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

    protected static class CheckXPathCopyToFilter implements CopyToFilter {

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

    }

}
