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

import java.util.Iterator;
import java.util.NoSuchElementException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jaxen.Context;
import org.jaxen.ContextSupport;
import org.jaxen.JaxenException;
import org.jaxen.NamespaceContext;
import org.jaxen.SimpleNamespaceContext;
import org.jaxen.SimpleVariableContext;
import org.jaxen.XPath;
import org.jaxen.XPathFunctionContext;
import org.jaxen.pattern.Pattern;
import org.jaxen.pattern.PatternParser;
import org.jaxen.saxpath.SAXPathException;
import org.seasar.mayaa.engine.specification.Namespace;
import org.seasar.mayaa.engine.specification.Specification;
import org.seasar.mayaa.engine.specification.SpecificationNode;
import org.seasar.mayaa.impl.engine.EngineUtil;
import org.seasar.mayaa.impl.engine.specification.SpecificationUtil;
import org.seasar.mayaa.impl.util.StringUtil;
import org.seasar.mayaa.impl.util.collection.NullIterator;

/**
 * @author Masataka Kurihara (Gluegent, Inc.)
 */
public class XPathUtil {
    private static final Log LOG = LogFactory.getLog(XPathUtil.class);

    private XPathUtil() {
        // no instantiation.
    }

    public static boolean matches(SpecificationNode test,
            String xpathExpr, Namespace namespace) {
        if (StringUtil.isEmpty(xpathExpr)) {
            throw new IllegalArgumentException();
        }
        NamespaceContext nsContext;
        if (namespace == null) {
            nsContext = new SimpleNamespaceContext();
        } else {
            nsContext = new NamespaceContextImpl(namespace);
        }
        ContextSupport support = new ContextSupport(
                nsContext,
                XPathFunctionContext.getInstance(),
                new SimpleVariableContext(),
                SpecificationNavigator.getInstance());
        Context context = new Context(support);
        try {
            Pattern pattern = PatternParser.parse(xpathExpr);
            return pattern.matches(test, context);
        } catch (JaxenException e) {
            throw new RuntimeException(e);
        } catch (SAXPathException e) {
            throw new RuntimeException(e);
        }
    }

    public static Iterator selectChildNodes(SpecificationNode node,
            String xpathExpr, Namespace namespaceable, boolean cascade) {
        Specification specification = SpecificationUtil.findSpecification(node);
        if (specification == null) {
            LOG.warn("node top is not specification." + node.toString());
            return NullIterator.getInstance();
        }
        if (StringUtil.isEmpty(xpathExpr)) {
            throw new IllegalArgumentException();
        }
        if (cascade) {
            return new CascadeSelectNodesIterator(
                    specification, xpathExpr, namespaceable);
        }
        try {
            XPath xpath =
                SpecificationXPath.createXPath(xpathExpr, namespaceable);
            return xpath.selectNodes(specification).iterator();
        } catch (JaxenException e) {
            throw new RuntimeException(e);
        }
    }

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

    public static class CascadeSelectNodesIterator
            implements Iterator {

        private Specification _specification;
        private String _xpathExpr;
        private Namespace _namespaceable;
        private Iterator _iterator;

        public CascadeSelectNodesIterator(Specification specification,
                String xpathExpr, Namespace namespaceable) {
            if (specification == null || StringUtil.isEmpty(xpathExpr)) {
                throw new IllegalArgumentException();
            }
            _specification = specification;
            _xpathExpr = xpathExpr;
            _namespaceable = namespaceable;
        }

        public boolean hasNext() {
            while (true) {
                if (_iterator == null) {
                    XPath xpath = SpecificationXPath.createXPath(
                            _xpathExpr, _namespaceable);
                    try {
                        _iterator = xpath.selectNodes(_specification).iterator();
                    } catch (JaxenException e) {
                        throw new RuntimeException(e);
                    }
                }
                if (_iterator.hasNext()) {
                    return true;
                }
                Specification parent =
                    EngineUtil.getParentSpecification(_specification);
                if (parent == null) {
                    return false;
                }
                _specification = parent;
                _iterator = null;
            }
        }

        public Object next() {
            if (hasNext()) {
                return _iterator.next();
            }
            throw new NoSuchElementException();
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }

    }

}
