/*
 * 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.wicket.util.visit;

import java.util.Collections;
import java.util.Iterator;

import org.apache.wicket.util.lang.Args;

/**
 * Utility class that contains visitor/traversal related code
 */
public class Visits
{
	/** Constructor */
	private Visits()
	{
	}

	private static class SingletonIterable<T> implements Iterable<T>
	{
		private final T singleton;

		public SingletonIterable(final T singleton)
		{
			this.singleton = singleton;
		}

		@Override
		public Iterator<T> iterator()
		{
			return Collections.singleton(singleton).iterator();
		}
	}

	/**
	 * Visits container and its children pre-order (parent first). Children are determined by
	 * calling {@link Iterable#iterator()}.
	 * 
	 * @param <S>
	 *            the type of object that will be visited, notice that {@code container} is not
	 *            declared as {@code Iterable<S>} because it may return a generalization of
	 *            {@code S}
	 * @param <R>
	 *            the type of object that should be returned from the visitor, use {@link Void} if
	 *            no return value is needed
	 * @param container
	 *            object whose children will be visited
	 * @param visitor
	 *            the visitor
	 * @return return value from the {@code visitor} or {@code null} if none
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static <S, R> R visit(final Iterable<? super S> container,
		final IVisitor<S, R> visitor)
	{
		return (R)visitChildren(new SingletonIterable(container), visitor, IVisitFilter.ANY);
	}

	/**
	 * Visits container and its children pre-order (parent first). Children are determined by
	 * calling {@link Iterable#iterator()}.
	 * 
	 * @param <S>
	 *            the type of object that will be visited, notice that {@code container} is not
	 *            declared as {@code Iterable<S>} because it may return a generalization of
	 *            {@code S}
	 * @param <R>
	 *            the type of object that should be returned from the visitor, use {@link Void} if
	 *            no return value is needed
	 * @param container
	 *            object whose children will be visited
	 * @param visitor
	 *            the visitor
	 * @param filter
	 *            filter used to limit the types of objects that will be visited
	 * @return return value from the {@code visitor} or {@code null} if none
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static <S, R> R visit(final Iterable<? super S> container,
		final IVisitor<S, R> visitor, final IVisitFilter filter)
	{
		return (R)visitChildren(new SingletonIterable(container), visitor, filter);
	}

	/**
	 * Visits children of the specified {@link Iterable} pre-order (parent first). Children are
	 * determined by calling {@link Iterable#iterator()}.
	 * 
	 * @param <S>
	 *            the type of object that will be visited, notice that {@code container} is not
	 *            declared as {@code Iterable<S>} because it may return a generalization of
	 *            {@code S}
	 * @param <R>
	 *            the type of object that should be returned from the visitor, use {@link Void} if
	 *            no return value is needed
	 * @param container
	 *            object whose children will be visited
	 * @param visitor
	 *            the visitor
	 * @param filter
	 *            filter used to limit the types of objects that will be visited
	 * @return return value from the {@code visitor} or {@code null} if none
	 */
	public static <S, R> R visitChildren(final Iterable<? super S> container,
		final IVisitor<S, R> visitor, final IVisitFilter filter)
	{
		Visit<R> visit = new Visit<>();
		visitChildren(container, visitor, filter, visit);
		return visit.getResult();
	}

	@SuppressWarnings("unchecked")
	private static <S, R> void visitChildren(final Iterable<? super S> container,
		final IVisitor<S, R> visitor, final IVisitFilter filter, final Visit<R> visit)
	{
		Args.notNull(visitor, "visitor");

		// Iterate through children of this container
		for (final Object child : container)
		{
			// Get next child component
			// Is the child of the correct class (or was no class specified)?
			if (filter.visitObject(child))
			{
				Visit<R> childTraversal = new Visit<>();

				// Call visitor
				S s = (S)child;
				visitor.component(s, childTraversal);

				if (childTraversal.isStopped())
				{
					visit.stop(childTraversal.getResult());
					return;
				}
				else if (childTraversal.isDontGoDeeper())
				{
					continue;
				}
			}

			// If child is a container
			if (!visit.isDontGoDeeper() && (child instanceof Iterable<?>) &&
				filter.visitChildren(child))
			{
				// visit the children in the container
				visitChildren((Iterable<? super S>)child, visitor, filter, visit);

				if (visit.isStopped())
				{
					return;
				}
			}
		}
	}

	/**
	 * Visits children of the specified {@link Iterable} pre-order (parent first). Children are
	 * determined by calling {@link Iterable#iterator()}.
	 * 
	 * @param <S>
	 *            the type of object that will be visited, notice that {@code container} is not
	 *            declared as {@code Iterable<S>} because it may return a generalization of
	 *            {@code S}
	 * @param <R>
	 *            the type of object that should be returned from the visitor, use {@link Void} if
	 *            no return value is needed
	 * @param container
	 *            object whose children will be visited
	 * @param visitor
	 *            the visitor
	 * @return return value from the {@code visitor} or {@code null} if none
	 */
	public static <S, R> R visitChildren(final Iterable<? super S> container,
		final IVisitor<S, R> visitor)
	{
		return visitChildren(container, visitor, IVisitFilter.ANY);
	}

	/**
	 * Visits the specified object and any of its children using a post-order (child first)
	 * traversal. Children are determined by calling {@link Iterable#iterator()} if the object
	 * implements {@link Iterable}.
	 * 
	 * @param <S>
	 *            the type of object that will be visited, notice that {@code container} is not
	 *            declared as {@code Iterable<S>} because it may return a generalization of
	 *            {@code S}
	 * @param <R>
	 *            the type of object that should be returned from the visitor, use {@link Void} if
	 *            no return value is needed
	 * @param root
	 *            root object that will be visited
	 * @param visitor
	 *            the visitor
	 * @return return value from the {@code visitor} or {@code null} if none
	 */
	public static <S, R> R visitPostOrder(final S root,
		final org.apache.wicket.util.visit.IVisitor<S, R> visitor)
	{
		return visitPostOrder(root, visitor, IVisitFilter.ANY);
	}

	/**
	 * Visits the specified object and any of its children using a post-order (child first)
	 * traversal. Children are determined by calling {@link Iterable#iterator()} if the object
	 * implements {@link Iterable}.
	 * 
	 * @param <S>
	 *            the type of object that will be visited, notice that {@code container} is not
	 *            declared as {@code Iterable<S>} because it may return a generalization of
	 *            {@code S}
	 * @param <R>
	 *            the type of object that should be returned from the visitor, use {@link Void} if
	 *            no return value is needed
	 * @param root
	 *            root object that will be visited
	 * @param visitor
	 *            the visitor
	 * @param filter
	 *            filter used to limit the types of objects that will be visited
	 * @return return value from the {@code visitor} or {@code null} if none
	 */
	public static <S, R> R visitPostOrder(final Object root,
		final org.apache.wicket.util.visit.IVisitor<S, R> visitor, final IVisitFilter filter)
	{
		Args.notNull(visitor, "visitor");

		Visit<R> visit = new Visit<>();
		visitPostOrderHelper(root, visitor, filter, visit);
		return visit.getResult();
	}

	@SuppressWarnings("unchecked")
	private static <S, R> void visitPostOrderHelper(final Object component,
		final org.apache.wicket.util.visit.IVisitor<S, R> visitor, final IVisitFilter filter,
		final Visit<R> visit)
	{
		if (component instanceof Iterable<?>)
		{
			final Iterable<?> container = (Iterable<?>)component;
			if (filter.visitChildren(container))
			{
				Visit<R> childTraversal = new Visit<>();
				for (final Object child : ((Iterable<?>)component))
				{
					visitPostOrderHelper(child, visitor, filter, childTraversal);
					if (childTraversal.isStopped())
					{
						visit.stop(childTraversal.getResult());
						return;
					}
				}
			}
		}

		if (filter.visitObject(component))
		{
			visitor.component((S)component, visit);
		}
	}
}
