/*******************************************************************************
 * Breakout Cave Survey Visualizer
 *
 * Copyright (C) 2014 James Edwards
 *
 * jedwards8 at fastmail dot fm
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation; either version 2 of the License, or (at your option) any later
 * version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 51
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *******************************************************************************/
package org.andork.reflect;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Set;

public class ObjectTraverser {
	public static class DefaultVisitorBase implements Visitor {
		int depth = 0;
		int maxDepth = 10000;

		Set<Instance> instances = new HashSet<Instance>();

		@Override
		public void exitObject(ObjectTraverser traverser, Object obj, Object parent, Field parentField, int index) {
			depth--;
		}

		@Override
		public boolean visitObject(ObjectTraverser traverser, Object obj, Object parent, Field parentField, int index) {
			if (depth == maxDepth) {
				return false;
			}

			if (obj != null && instances.add(new Instance(obj))) {
				depth++;
				return true;
			}

			return false;
		}

		@Override
		public void visitPrimitive(ObjectTraverser traverser, Object prim, Object parent, Field parentField) {
		}
	}

	public static final class Instance {
		final Object obj;

		protected Instance(Object obj) {
			super();
			this.obj = obj;
		}

		@Override
		public boolean equals(Object o) {
			if (o instanceof Instance) {
				return obj == ((Instance) o).obj;
			}
			return false;
		}

		@Override
		public int hashCode() {
			return System.identityHashCode(obj);
		}
	}

	public static interface Visitor {
		public void exitObject(ObjectTraverser traverser, Object obj, Object parent, Field parentField, int index);

		public boolean visitObject(ObjectTraverser traverser, Object obj, Object parent, Field parentField, int index);

		public void visitPrimitive(ObjectTraverser traverser, Object prim, Object parent, Field parentField);
	}

	private void traverse(Object obj, Object parent, Field parentField, Visitor visitor, int index) throws Exception {
		if (visitor.visitObject(this, obj, parent, parentField, index) && obj != null
				&& (parentField == null || !parentField.getType().isPrimitive())) {
			Class<?> oclass = obj.getClass();

			if (oclass.isArray()) {
				if (!oclass.getComponentType().isPrimitive()) {
					int length = Array.getLength(obj);
					for (int i = 0; i < length; i++) {
						traverse(Array.get(obj, i), parent, parentField, visitor, i);
					}
				}
			} else {
				while (oclass != null) {
					for (Field field : oclass.getDeclaredFields()) {
						if (Modifier.isStatic(field.getModifiers())) {
							continue;
						}

						field.setAccessible(true);
						Object value = field.get(obj);

						if (field.getType().isPrimitive()) {
							visitor.visitPrimitive(this, value, obj, field);
						} else {
							traverse(value, obj, field, visitor, -1);
						}
					}

					oclass = oclass.getSuperclass();
				}
			}

			visitor.exitObject(this, obj, parent, parentField, index);
		}
	}

	public void traverse(Object obj, Visitor visitor) throws Exception {
		traverse(obj, null, null, visitor, -1);
	}
}
