/*
 * 作成日： 2004/08/19
 */
package org.seasar.tapestry;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tapestry.ApplicationRuntimeException;
import org.apache.tapestry.BaseComponent;
import org.apache.tapestry.IAsset;
import org.apache.tapestry.IBinding;
import org.apache.tapestry.IComponent;
import org.apache.tapestry.IEngine;
import org.apache.tapestry.ILocation;
import org.apache.tapestry.INamespace;
import org.apache.tapestry.IPage;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.IResourceLocation;
import org.apache.tapestry.IResourceResolver;
import org.apache.tapestry.Tapestry;
import org.apache.tapestry.asset.ContextAsset;
import org.apache.tapestry.asset.ExternalAsset;
import org.apache.tapestry.asset.PrivateAsset;
import org.apache.tapestry.binding.ExpressionBinding;
import org.apache.tapestry.binding.FieldBinding;
import org.apache.tapestry.binding.ListenerBinding;
import org.apache.tapestry.binding.StaticBinding;
import org.apache.tapestry.binding.StringBinding;
import org.apache.tapestry.engine.IComponentClassEnhancer;
import org.apache.tapestry.engine.ITemplateSource;
import org.apache.tapestry.event.PageDetachListener;
import org.apache.tapestry.pageload.ComponentTreeWalker;
import org.apache.tapestry.pageload.EstablishDefaultParameterValuesVisitor;
import org.apache.tapestry.pageload.IComponentVisitor;
import org.apache.tapestry.pageload.PageLoader;
import org.apache.tapestry.pageload.PropertyInitializer;
import org.apache.tapestry.pageload.VerifyRequiredParametersVisitor;
import org.apache.tapestry.request.RequestContext;
import org.apache.tapestry.resolver.ComponentSpecificationResolver;
import org.apache.tapestry.resource.ClasspathResourceLocation;
import org.apache.tapestry.resource.ContextResourceLocation;
import org.apache.tapestry.spec.AssetType;
import org.apache.tapestry.spec.BindingType;
import org.apache.tapestry.spec.IAssetSpecification;
import org.apache.tapestry.spec.IBindingSpecification;
import org.apache.tapestry.spec.IComponentSpecification;
import org.apache.tapestry.spec.IContainedComponent;
import org.apache.tapestry.spec.IListenerBindingSpecification;
import org.apache.tapestry.spec.IPropertySpecification;

/**
 * org.apache.tapestry.pageload.PageLoaderをコピってます。
 * 下策なのでだれかいい案たのむ。
 * @author maruo_syunsuke
 */
public abstract class CopyOfPageLoader extends PageLoader {
	private IPage instantiatePage(String name, INamespace namespace,
			IComponentSpecification spec) {
		IPage result = null;

		String pageName = namespace.constructQualifiedName(name);
		String className = spec.getComponentClassName();
		ILocation location = spec.getLocation();

		if (Tapestry.isBlank(className)) {
			if (LOG.isDebugEnabled())
				LOG.debug("Page " + namespace.constructQualifiedName(name)
						+ " does not specify a component class.");

			className = _engine.getPropertySource().getPropertyValue(
					"org.apache.tapestry.default-page-class");

			if (className == null) {
				className = getDefaultPage();// diff
			}

			if (LOG.isDebugEnabled())
				LOG.debug("Defaulting to class " + className);
		}

		Class pageClass = _enhancer.getEnhancedClass(spec, className);
		String enhancedClassName = pageClass.getName();

		try {
			result = getPageComponent(pageClass); // diff

			result.setNamespace(namespace);
			result.setSpecification(spec);
			result.setPageName(pageName);
			result.setPage(result);
			result.setLocale(_locale);
			result.setLocation(location);
		} catch (ClassCastException ex) {
			throw new ApplicationRuntimeException(Tapestry.format(
					"PageLoader.class-not-page", enhancedClassName), location,
					ex);
		} catch (Exception ex) {
			throw new ApplicationRuntimeException(Tapestry.format(
					"PageLoader.unable-to-instantiate", enhancedClassName),
					location, ex);
		}

		return result;
	}

	/**
	 * @return
	 */
	public abstract String getDefaultPage() ;

	/**
	 * @param pageClass
	 * @return 表示するページファイルを返します。
	 */
	public abstract IPage getPageComponent(Class pageClass) throws Exception;

	//==========================================================================
	//	The code under this is a copy from org.apache.tapestry.pageload.PageLoader
	//==========================================================================

	private static final Log LOG = LogFactory.getLog(PageLoader.class);

	private IEngine _engine ;

	private IResourceResolver _resolver;

	private IComponentClassEnhancer _enhancer;

	private ComponentSpecificationResolver _componentResolver;

	private List _inheritedBindingQueue = new ArrayList();

	private List _propertyInitializers = new ArrayList();

	private ComponentTreeWalker _establishDefaultParameterValuesWalker;

	private ComponentTreeWalker _verifyRequiredParametersWalker;

	/**
	 * The locale of the application, which is also the locale of the page being
	 * loaded.
	 *  
	 */

	private Locale _locale;

	/**
	 * Number of components instantiated, excluding the page itself.
	 *  
	 */

	private int _count;

	/**
	 * The recursion depth. A page with no components is zero. A component on a
	 * page is one.
	 *  
	 */

	private int _depth;

	/**
	 * The maximum depth reached while building the page.
	 *  
	 */

	private int _maxDepth;

	/**
	 * Used to figure relative paths for context assets.
	 *  
	 */

	private IResourceLocation _servletLocation;

	private static interface IQueuedInheritedBinding {
		void connect();
	}

	private static class QueuedInheritedBinding implements
			IQueuedInheritedBinding {
		private IComponent _component;

		private String _containerParameterName;

		private String _parameterName;

		private QueuedInheritedBinding(IComponent component,
				String containerParameterName, String parameterName) {
			_component = component;
			_containerParameterName = containerParameterName;
			_parameterName = parameterName;
		}

		public void connect() {
			IBinding binding = _component.getContainer().getBinding(
					_containerParameterName);

			if (binding == null)
				return;

			_component.setBinding(_parameterName, binding);
		}
	}

	private static class QueuedInheritInformalBindings implements
			IQueuedInheritedBinding {
		private IComponent _component;

		private QueuedInheritInformalBindings(IComponent component) {
			_component = component;
		}

		public void connect() {

			IComponent container = _component.getContainer();

			for (Iterator it = container.getBindingNames().iterator(); it
					.hasNext();) {
				String bindingName = (String) it.next();
				connectInformalBinding(container, _component, bindingName);
			}
		}

		private void connectInformalBinding(IComponent container,
				IComponent component, String bindingName) {
			IComponentSpecification componentSpec = component
					.getSpecification();
			IComponentSpecification containerSpec = container
					.getSpecification();

			// check if binding already exists in the component
			if (component.getBinding(bindingName) != null)
				return;

			// check if parameter is informal for the component
			if (componentSpec.getParameter(bindingName) != null
					|| componentSpec.isReservedParameterName(bindingName))
				return;

			// check if parameter is informal for the container
			if (containerSpec.getParameter(bindingName) != null
					|| containerSpec.isReservedParameterName(bindingName))
				return;

			// if everything passes, establish binding
			IBinding binding = container.getBinding(bindingName);
			component.setBinding(bindingName, binding);
		}
	}

	/**
	 * Constructor.
	 *  
	 */

	public CopyOfPageLoader(IRequestCycle cycle) {
		super(cycle);
		
		IEngine engine = cycle.getEngine();

		_resolver = engine.getResourceResolver();
		_enhancer = engine.getComponentClassEnhancer();
		_componentResolver = new ComponentSpecificationResolver(cycle);

		RequestContext context = cycle.getRequestContext();

		// Need the location of the servlet within the context as the basis
		// for building relative context asset paths.

		HttpServletRequest request = context.getRequest();

		String servletPath = request.getServletPath();

		_servletLocation = new ContextResourceLocation(context.getServlet()
				.getServletContext(), servletPath);

		// Create the mechanisms for walking the component tree when it is
		// complete
		IComponentVisitor verifyRequiredParametersVisitor = new VerifyRequiredParametersVisitor();
		_verifyRequiredParametersWalker = new ComponentTreeWalker(
				new IComponentVisitor[] { verifyRequiredParametersVisitor });

		IComponentVisitor establishDefaultParameterValuesVisitor = new EstablishDefaultParameterValuesVisitor(
				_resolver);
		_establishDefaultParameterValuesWalker = new ComponentTreeWalker(
				new IComponentVisitor[] { establishDefaultParameterValuesVisitor });
	}

	/**
	 * Binds properties of the component as defined by the container's
	 * specification.
	 * 
	 * <p>
	 * This implementation is very simple, we will need a lot more sanity
	 * checking and eror checking in the final version.
	 * 
	 * @param container
	 *            The containing component. For a dynamic binding (
	 *            {@link ExpressionBinding}) the property name is evaluated
	 *            with the container as the root.
	 * @param component
	 *            The contained component being bound.
	 * @param spec
	 *            The specification of the contained component.
	 * @param contained
	 *            The contained component specification (from the container's
	 *            {@link IComponentSpecification}).
	 *  
	 */

	private void bind(IComponent container, IComponent component,
			IContainedComponent contained) {
		IComponentSpecification spec = component.getSpecification();
		boolean formalOnly = !spec.getAllowInformalParameters();

		IComponentSpecification containerSpec = container.getSpecification();
		boolean containerFormalOnly = !containerSpec
				.getAllowInformalParameters();

		if (contained.getInheritInformalParameters()) {
			if (formalOnly)
				throw new ApplicationRuntimeException(
						Tapestry
								.format(
										"PageLoader.inherit-informal-invalid-component-formal-only",
										component.getExtendedId()), component,
						contained.getLocation(), null);

			if (containerFormalOnly)
				throw new ApplicationRuntimeException(
						Tapestry
								.format(
										"PageLoader.inherit-informal-invalid-container-formal-only",
										container.getExtendedId(), component
												.getExtendedId()), component,
						contained.getLocation(), null);

			IQueuedInheritedBinding queued = new QueuedInheritInformalBindings(
					component);
			_inheritedBindingQueue.add(queued);
		}

		Iterator i = contained.getBindingNames().iterator();

		while (i.hasNext()) {
			String name = (String) i.next();

			boolean isFormal = spec.getParameter(name) != null;

			IBindingSpecification bspec = contained.getBinding(name);

			// If not allowing informal parameters, check that each binding
			// matches
			// a formal parameter.

			if (formalOnly && !isFormal)
				throw new ApplicationRuntimeException(Tapestry.format(
						"PageLoader.formal-parameters-only", component
								.getExtendedId(), name), component, bspec
						.getLocation(), null);

			// If an informal parameter that conflicts with a reserved name,
			// then
			// skip it.

			if (!isFormal && spec.isReservedParameterName(name))
				continue;

			// The type determines how to interpret the value:
			// As a simple static String
			// As a nested property name (relative to the component)
			// As the name of a binding inherited from the containing component.
			// As the name of a public field
			// As a script for a listener

			BindingType type = bspec.getType();

			// For inherited bindings, defer until later. This gives components
			// a chance to setup bindings from static values and expressions in
			// the
			// template. The order of operations is tricky, template bindings
			// come
			// later.

			if (type == BindingType.INHERITED) {
				QueuedInheritedBinding queued = new QueuedInheritedBinding(
						component, bspec.getValue(), name);
				_inheritedBindingQueue.add(queued);
				continue;
			}

			if (type == BindingType.LISTENER) {
				constructListenerBinding(component, name,
						(IListenerBindingSpecification) bspec);
				continue;
			}

			IBinding binding = convert(container, bspec);

			if (binding != null)
				component.setBinding(name, binding);
		}
	}

	private IBinding convert(IComponent container, IBindingSpecification spec) {
		BindingType type = spec.getType();
		ILocation location = spec.getLocation();
		String value = spec.getValue();

		// The most common type.
		// TODO These bindings should be created somehow using the SpecFactory
		// in SpecificationParser
		if (type == BindingType.DYNAMIC)
			return new ExpressionBinding(_resolver, container, value, location);

		// String bindings are new in 2.0.4. For the momement,
		// we don't even try to cache and share them ... they
		// are most often unique within a page.

		if (type == BindingType.STRING)
			return new StringBinding(container, value, location);

		// static and field bindings are pooled. This allows the
		// same instance to be used with many components.

		if (type == BindingType.STATIC)
			return new StaticBinding(value, location);

		// BindingType.FIELD is on the way out, it is in the
		// 1.3 DTD but not the 1.4 DTD.

		if (type == BindingType.FIELD)
			return new FieldBinding(_resolver, value, location);

		// This code is unreachable, at least until a new type
		// of binding is created.

		throw new ApplicationRuntimeException("Unexpected type: " + type + ".");
	}

	/**
	 * Construct a {@link ListenerBinding}for the component, and add it.
	 * 
	 * @since 3.0
	 *  
	 */

	private void constructListenerBinding(IComponent component,
			String bindingName, IListenerBindingSpecification spec) {
		String language = spec.getLanguage();

		// If not provided in the page or component specification, then
		// search for a default (factory default is "jython").

		if (Tapestry.isBlank(language))
			language = _engine.getPropertySource().getPropertyValue(
					"org.apache.tapestry.default-script-language");

		// Construct the binding. The first parameter is the compononent
		// (not the DirectLink or Form, but the page or component containing the
		// link or form).

		IBinding binding = new ListenerBinding(component.getContainer(),
				language, spec.getScript(), spec.getLocation());

		component.setBinding(bindingName, binding);
	}

	/**
	 * Sets up a component. This involves:
	 * <ul>
	 * <li>Instantiating any contained components.
	 * <li>Add the contained components to the container.
	 * <li>Setting up bindings between container and containees.
	 * <li>Construct the containees recursively.
	 * <li>Invoking
	 * {@link IComponent#finishLoad(IRequestCycle, IPageLoader, IComponentSpecification)}
	 * </ul>
	 * 
	 * @param cycle
	 *            the request cycle for which the page is being (initially)
	 *            constructed
	 * @param page
	 *            The page on which the container exists.
	 * @param container
	 *            The component to be set up.
	 * @param containerSpec
	 *            The specification for the container.
	 * @param the
	 *            namespace of the container
	 *  
	 */

	private void constructComponent(IRequestCycle cycle, IPage page,
			IComponent container, IComponentSpecification containerSpec,
			INamespace namespace) {
		_depth++;
		if (_depth > _maxDepth)
			_maxDepth = _depth;

		List ids = new ArrayList(containerSpec.getComponentIds());
		int count = ids.size();

		try {
			for (int i = 0; i < count; i++) {
				String id = (String) ids.get(i);

				// Get the sub-component specification from the
				// container's specification.

				IContainedComponent contained = containerSpec.getComponent(id);

				String type = contained.getType();
				ILocation location = contained.getLocation();

				_componentResolver.resolve(cycle, namespace, type, location);

				IComponentSpecification componentSpecification = _componentResolver
						.getSpecification();
				INamespace componentNamespace = _componentResolver
						.getNamespace();

				// Instantiate the contained component.

				IComponent component = instantiateComponent(page, container,
						id, componentSpecification, componentNamespace,
						location);

				// Add it, by name, to the container.

				container.addComponent(component);

				// Set up any bindings in the IContainedComponent specification

				bind(container, component, contained);

				// Now construct the component recusively; it gets its chance
				// to create its subcomponents and set their bindings.

				constructComponent(cycle, page, component,
						componentSpecification, componentNamespace);
			}

			addAssets(container, containerSpec);

			// Finish the load of the component; most components (which
			// subclass BaseComponent) load their templates here.
			// That may cause yet more components to be created, and more
			// bindings to be set, so we defer some checking until
			// later.

			container.finishLoad(cycle, this, containerSpec);

			// Finally, we create an initializer for each
			// specified property.

			createPropertyInitializers(page, container, containerSpec);
		} catch (ApplicationRuntimeException ex) {
			throw ex;
		} catch (RuntimeException ex) {
			throw new ApplicationRuntimeException(Tapestry.format(
					"PageLoader.unable-to-instantiate-component", container
							.getExtendedId(), ex.getMessage()), container,
					null, ex);
		}

		_depth--;
	}

	/**
	 * Invoked to create an implicit component (one which is defined in the
	 * containing component's template, rather that in the containing
	 * component's specification).
	 * 
	 * @see org.apache.tapestry.BaseComponentTemplateLoader
	 * @since 3.0
	 *  
	 */

	public IComponent createImplicitComponent(IRequestCycle cycle,
			IComponent container, String componentId, String componentType,
			ILocation location) {
		IPage page = container.getPage();

		_componentResolver.resolve(cycle, container.getNamespace(),
				componentType, location);

		INamespace componentNamespace = _componentResolver.getNamespace();
		IComponentSpecification spec = _componentResolver.getSpecification();

		IComponent result = instantiateComponent(page, container, componentId,
				spec, componentNamespace, location);

		container.addComponent(result);

		// Recusively build the component.

		constructComponent(cycle, page, result, spec, componentNamespace);

		return result;
	}

	/**
	 * Instantiates a component from its specification. We instantiate the
	 * component object, then set its specification, page, container and id.
	 * 
	 * @see AbstractComponent
	 *  
	 */

	private IComponent instantiateComponent(IPage page, IComponent container,
			String id, IComponentSpecification spec, INamespace namespace,
			ILocation location) {
		IComponent result = null;
		String className = spec.getComponentClassName();

		if (Tapestry.isBlank(className))
			className = BaseComponent.class.getName();

		Class componentClass = _enhancer.getEnhancedClass(spec, className);
		String enhancedClassName = componentClass.getName();

		try {
			result = (IComponent) componentClass.newInstance();

		} catch (ClassCastException ex) {
			throw new ApplicationRuntimeException(Tapestry.format(
					"PageLoader.class-not-component", enhancedClassName),
					container, spec.getLocation(), ex);
		} catch (Throwable ex) {
			throw new ApplicationRuntimeException(Tapestry.format(
					"PageLoader.unable-to-instantiate", enhancedClassName),
					container, spec.getLocation(), ex);
		}

		if (result instanceof IPage)
			throw new ApplicationRuntimeException(Tapestry.format(
					"PageLoader.page-not-allowed", result.getExtendedId()),
					result, null, null);

		result.setNamespace(namespace);
		result.setSpecification(spec);
		result.setPage(page);
		result.setContainer(container);
		result.setId(id);
		result.setLocation(location);

		_count++;

		return result;
	}

	/**
	 * Invoked by the {@link PageSource}to load a specific page. This method is
	 * not reentrant ... the PageSource ensures that any given instance of
	 * PageLoader is loading only a single page at a time. The page is
	 * immediately attached to the {@link IEngine engine}.
	 * 
	 * @param name
	 *            the simple (unqualified) name of the page to load
	 * @param namespace
	 *            from which the page is to be loaded (used when resolving
	 *            components embedded by the page)
	 * @param cycle
	 *            the request cycle the page is initially loaded for (this is
	 *            used to define the locale of the new page, and provide access
	 *            to the corect specification source, etc.).
	 * @param specification
	 *            the specification for the page
	 *  
	 */

	public IPage loadPage(String name, INamespace namespace,
			IRequestCycle cycle, IComponentSpecification specification) {
		IPage page = null;

		_engine = cycle.getEngine();

		_locale = _engine.getLocale();

		_count = 0;
		_depth = 0;
		_maxDepth = 0;

		try {
			page = instantiatePage(name, namespace, specification);

			page.attach(_engine);

			constructComponent(cycle, page, page, specification, namespace);

			// Walk through the complete component tree to set up the default
			// parameter values.
			_establishDefaultParameterValuesWalker.walkComponentTree(page);

			establishInheritedBindings();

			// Walk through the complete component tree to ensure that required
			// parameters are bound
			_verifyRequiredParametersWalker.walkComponentTree(page);

			establishDefaultPropertyValues();
		} finally {
			_locale = null;
			_engine = null;
			_inheritedBindingQueue.clear();
			_propertyInitializers.clear();
		}

		if (LOG.isDebugEnabled())
			LOG.debug("Loaded page " + page + " with " + _count
					+ " components (maximum depth " + _maxDepth + ")");

		return page;
	}

	private void establishInheritedBindings() {
		LOG.debug("Establishing inherited bindings");

		int count = _inheritedBindingQueue.size();

		for (int i = 0; i < count; i++) {
			IQueuedInheritedBinding queued = (IQueuedInheritedBinding) _inheritedBindingQueue
					.get(i);

			queued.connect();
		}
	}

	private void establishDefaultPropertyValues() {
		LOG.debug("Setting default property values");

		int count = _propertyInitializers.size();

		for (int i = 0; i < count; i++) {
			PageDetachListener initializer = (PageDetachListener) _propertyInitializers
					.get(i);

			initializer.pageDetached(null);
		}
	}

	private void addAssets(IComponent component,
			IComponentSpecification specification) {
		List names = specification.getAssetNames();

		if (names.isEmpty())
			return;

		IResourceLocation specLocation = specification
				.getSpecificationLocation();

		Iterator i = names.iterator();

		while (i.hasNext()) {
			String name = (String) i.next();
			IAssetSpecification assetSpec = specification.getAsset(name);
			IAsset asset = convert(name, component, assetSpec, specLocation);

			component.addAsset(name, asset);
		}
	}

	/**
	 * Invoked from
	 * {@link #constructComponent(IRequestCycle, IPage, IComponent, IComponentSpecification, INamespace)}
	 * after
	 * {@link IComponent#finishLoad(IRequestCycle, IPageLoader, IComponentSpecification)}
	 * is invoked. This iterates over any
	 * {@link org.apache.tapestry.spec.IPropertySpecification}s for the
	 * component, create an initializer for each.
	 *  
	 */

	private void createPropertyInitializers(IPage page, IComponent component,
			IComponentSpecification spec) {
		List names = spec.getPropertySpecificationNames();
		int count = names.size();

		for (int i = 0; i < count; i++) {
			String name = (String) names.get(i);
			IPropertySpecification ps = spec.getPropertySpecification(name);
			String expression = ps.getInitialValue();

			PageDetachListener initializer = new PropertyInitializer(_resolver,
					component, name, expression, ps.getLocation());

			_propertyInitializers.add(initializer);
			page.addPageDetachListener(initializer);
		}

	}

	/**
	 * Builds an instance of {@link IAsset}from the specification.
	 *  
	 */

	private IAsset convert(String assetName, IComponent component,
			IAssetSpecification spec, IResourceLocation specificationLocation) {
		AssetType type = spec.getType();
		String path = spec.getPath();
		ILocation location = spec.getLocation();

		if (type == AssetType.EXTERNAL)
			return new ExternalAsset(path, location);

		if (type == AssetType.PRIVATE) {
			IResourceLocation baseLocation = specificationLocation;

			// Fudge a special case for private assets with complete paths. The
			// specificationLocation
			// can't be used because it is often a ContextResourceLocation,
			// not a ClasspathResourceLocation.

			if (path.startsWith("/")) {
				baseLocation = new ClasspathResourceLocation(_resolver, "/");
				path = path.substring(1);
			}

			return new PrivateAsset((ClasspathResourceLocation) findAsset(
					assetName, component, baseLocation, path, location),
					location);
		}

		return new ContextAsset((ContextResourceLocation) findAsset(assetName,
				component, _servletLocation, path, location), location);
	}

	private IResourceLocation findAsset(String assetName, IComponent component,
			IResourceLocation baseLocation, String path, ILocation location) {
		IResourceLocation assetLocation = baseLocation
				.getRelativeLocation(path);
		IResourceLocation localizedLocation = assetLocation
				.getLocalization(_locale);

		if (localizedLocation == null)
			throw new ApplicationRuntimeException(Tapestry.format(
					"PageLoader.missing-asset", assetName, component
							.getExtendedId(), assetLocation), component,
					location, null);

		return localizedLocation;
	}

	public IEngine getEngine() {
		return _engine;
	}

	public ITemplateSource getTemplateSource() {
		return _engine.getTemplateSource();
	}
}