/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.commons.configuration2.tree.xpath;

import org.apache.commons.configuration2.tree.NodeHandler;
import org.apache.commons.jxpath.ri.QName;
import org.apache.commons.jxpath.ri.model.NodeIterator;
import org.apache.commons.jxpath.ri.model.NodePointer;
import org.apache.commons.lang3.StringUtils;

/**
 * <p>
 * A base class for implementing iterators over configuration nodes.
 * </p>
 * <p>
 * This class already provides common functionality for implementing the
 * iteration process. Derived classes will implement specific behavior based on
 * the concrete node type (child node or attribute node).
 * </p>
 *
 * @since 1.3
 * @param <T> the type of the nodes this iterator deals with
 */
abstract class ConfigurationNodeIteratorBase<T> implements NodeIterator
{
    /** Constant for the prefix separator. */
    private static final String PREFIX_SEPARATOR = ":";

    /** A format for constructing a node name with a namespace prefix. */
    private static final String FMT_NAMESPACE = "%s" + PREFIX_SEPARATOR + "%s";

    /** Stores the parent node pointer. */
    private final ConfigurationNodePointer<T> parent;

    /** Stores the current position. */
    private int position;

    /** Stores the start offset of the iterator. */
    private int startOffset;

    /** Stores the reverse flag. */
    private final boolean reverse;

    /**
     * Creates a new instance of {@code ConfigurationNodeIteratorBase}
     * and initializes it.
     *
     * @param parent the parent pointer
     * @param reverse the reverse flag
     */
    protected ConfigurationNodeIteratorBase(final ConfigurationNodePointer<T> parent,
            final boolean reverse)
    {
        this.parent = parent;
        this.reverse = reverse;
    }

    /**
     * Returns the position of the iteration.
     *
     * @return the position
     */
    @Override
    public int getPosition()
    {
        return position;
    }

    /**
     * Sets the position of the iteration.
     *
     * @param pos the new position
     * @return a flag if this is a valid position
     */
    @Override
    public boolean setPosition(final int pos)
    {
        position = pos;
        return pos >= 1 && pos <= getMaxPosition();
    }

    /**
     * Returns the current node pointer.
     *
     * @return the current pointer in this iteration
     */
    @Override
    public NodePointer getNodePointer()
    {
        if (getPosition() < 1 && !setPosition(1))
        {
            return null;
        }

        return createNodePointer(positionToIndex(getPosition()));
    }

    /**
     * Returns the parent node pointer.
     *
     * @return the parent node pointer
     */
    protected ConfigurationNodePointer<T> getParent()
    {
        return parent;
    }

    /**
     * Returns the node handler for the managed nodes. This is a convenience
     * method.
     *
     * @return the node handler
     */
    protected NodeHandler<T> getNodeHandler()
    {
        return getParent().getNodeHandler();
    }

    /**
     * Returns the start offset of the iteration.
     *
     * @return the start offset
     */
    protected int getStartOffset()
    {
        return startOffset;
    }

    /**
     * Sets the start offset of the iteration. This is used when a start element
     * was set.
     *
     * @param startOffset the start offset
     */
    protected void setStartOffset(final int startOffset)
    {
        this.startOffset = startOffset;
        if (reverse)
        {
            this.startOffset--;
        }
        else
        {
            this.startOffset++;
        }
    }

    /**
     * Returns the maximum position for this iterator.
     *
     * @return the maximum allowed position
     */
    protected int getMaxPosition()
    {
        return reverse ? getStartOffset() + 1 : size() - getStartOffset();
    }

    /**
     * Returns the index in the data list for the given position. This method
     * also checks the reverse flag.
     *
     * @param pos the position (1-based)
     * @return the corresponding list index
     */
    protected int positionToIndex(final int pos)
    {
        return (reverse ? 1 - pos : pos - 1) + getStartOffset();
    }

    /**
     * Creates the configuration node pointer for the current position. This
     * method is called by {@code getNodePointer()}. Derived classes
     * must create the correct pointer object.
     *
     * @param position the current position in the iteration
     * @return the node pointer
     */
    protected abstract NodePointer createNodePointer(int position);

    /**
     * Returns the number of elements in this iteration.
     *
     * @return the number of elements
     */
    protected abstract int size();

    /**
     * Generates a qualified name with a namespace prefix.
     *
     * @param prefix the prefix
     * @param name the name (may be <b>null</b>)
     * @return the qualified name
     */
    protected static String prefixName(final String prefix, final String name)
    {
        return String.format(FMT_NAMESPACE, prefix,
                StringUtils.defaultString(name));
    }

    /**
     * Returns the qualified name from the given {@code QName}. If the name has
     * no namespace, result is the simple name. Otherwise, the namespace prefix
     * is added.
     *
     * @param name the {@code QName}
     * @return the qualified name
     */
    protected static String qualifiedName(final QName name)
    {
        return name.getPrefix() == null ? name.getName() : prefixName(
                name.getPrefix(), name.getName());
    }
}
