/*
 * Created on Apr 23, 2005
 *
 * $Log$
 * Revision 1.4  2005/09/21 22:00:45  dsowen
 * Split out the access stuff into accesslib.
 * New Creator interface off-loads object creation to user.
 *
 * Revision 1.3  2005/08/19 17:51:17  dsowen
 * Fixed: NPEs when using accessors from paths, &c.  Included tests.
 * Fixed: parsing exception wasn't very enlightening.
 *
 * Revision 1.2  2005/08/12 19:01:46  dsowen
 * Feature: can automatically fill missing parts of a graph.
 *
 * Revision 1.1  2005/08/03 00:35:29  dsowen
 * Initial commit.
 *
 * Revision 1.2  2005/04/25 02:45:56  dowen
 * Added type reporting to support creation.
 *
 * Revision 1.1  2005/04/25 01:39:53  dowen
 * Preliminary path support.
 *
 */
package ws.fugue88.jpath;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import ws.fugue88.access.Accessor;
import ws.fugue88.access.ListAccessor;
import ws.fugue88.access.MapAccessor;

/**
 * @author dsowen
 */
public class Context {

	public Accessor getTargetAccessor(final Path path)
			throws BrokenGraphException, IllegalAccessException,
			InstantiationException, InvocationTargetException,
			NoSuchPropertyException
	{
		Context current = this;
		for(Iterator i = path.parents().iterator(); i.hasNext();) {
			final PathPart part = (PathPart)i.next();
			Accessor accessor = current.getTargetAccessor(part);
			Object obj = accessor.getValue();
			if(obj == null)
					obj = _creator.create(accessor.getType(), current._root,
							part);
			if(obj == null)
					throw new BrokenGraphException(this, path, current._root,
							part);

			accessor.setValue(obj);
			current = new Context(obj, _binder, _creator);
		}
		return current.getTargetAccessor(path.terminal());
	}

	public Object getTarget(final Path path) throws IllegalAccessException,
			InstantiationException, InvocationTargetException,
			NoSuchPropertyException
	{
		try {
			return getTargetAccessor(path).getValue();
		} catch(BrokenGraphException e) {
			return null;
		}
	}

	public Context navigate(final Path path) throws IllegalAccessException,
			InstantiationException, InvocationTargetException,
			NoSuchPropertyException
	{
		Object child = getTarget(path);
		return new Context(child, _binder, _creator);
	}

	public Object getRoot()
	{
		return _root;
	}

	Context(final Object root, final Binder binder, final GraphCreator creator)
	{
		// NPEs
		root.getClass();
		binder.getClass();
		creator.getClass();

		_root = root;
		_binder = binder;
		_creator = creator;
	}

	private Accessor getTargetAccessor(final PathPart part)
			throws NoSuchPropertyException
	{
		if(part instanceof Identifier)
				return getTargetAccessor((Identifier)part);
		return getTargetAccessor((Selector)part);
	}

	private Accessor getTargetAccessor(final Identifier ident)
			throws NoSuchPropertyException
	{
		return new PropertyAccessor(_root, _binder.getProperty(
				_root.getClass(), ident));
	}

	private Accessor getTargetAccessor(final Selector sel)
	{
		if(_root instanceof Map)
				return new MapAccessor((Map)_root, sel.getKey());
		return new ListAccessor((List)_root, ((NumberSelector)sel).getIndex());
	}

	private Object createBlank(final Class type) throws IllegalAccessException,
			InstantiationException, InvocationTargetException
	{
		try {
			final Constructor ctor = type.getDeclaredConstructor(null);
			ctor.setAccessible(true);
			return ctor.newInstance(null);
		} catch(NoSuchMethodException e) {
			throw new InstantiationException("Class " + type.getName()
					+ " must have a no-argument constructor.");
		}
	}

	private final Object _root;
	private final Binder _binder;
	private final GraphCreator _creator;
}