//  Copyright 2004 The Apache Software Foundation
//
// Licensed 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.tapestry.resolver;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tapestry.ApplicationRuntimeException;
import org.apache.tapestry.ILocation;
import org.apache.tapestry.INamespace;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.IResourceLocation;
import org.apache.tapestry.Tapestry;
import org.apache.tapestry.spec.IComponentSpecification;

/**
 *  Utility class that understands the rules of component types (which
 *  may optionally have a library prefix) and can resolve 
 *  the type to a {@link org.apache.tapestry.INamespace} and a 
 *  {@link org.apache.tapestry.spec.IComponentSpecification}.
 * 
 *  <p>Like {@link org.apache.tapestry.resolver.PageSpecificationResolver},
 *  if the component is not defined explicitly in the namespace, a search
 *  may occur:
 * 
 *  Performs the tricky work of resolving a page name to a page specification.
 *  The search for pages in the application namespace is the most complicated,
 *  since Tapestry searches for pages that aren't explicitly defined in the
 *  application specification.  The search, based on the <i>simple-name</i>
 *  of the page, goes as follows:
 * 
 *  <ul>
 *  <li>As declared in the application specification
 *  <li><i>type</i>.jwc in the same folder as the application specification
 *  <li><i>type</i> jwc in the WEB-INF/<i>servlet-name</i> directory of the context root
 *  <li><i>type</i>.jwc in WEB-INF
 *  <li><i>type</i>.jwc in the application root (within the context root)
 *  <li>By searching the framework namespace
 *  </ul> 
 * 
 *  The search for components in library namespaces is more abbreviated:
 *  <li>As declared in the library specification
 *  <li><i>type</i>.jwc in the same folder as the library specification
 *  <li>By searching the framework namespace
 *  </ul>
 *
 * 
 *  @author Howard Lewis Ship
 *  @version $Id: ComponentSpecificationResolver.java 243791 2004-02-19 17:38:13Z hlship $
 *  @since 3.0
 *
 **/

public class ComponentSpecificationResolver extends AbstractSpecificationResolver
{
    private static final Log LOG = LogFactory.getLog(ComponentSpecificationResolver.class);

    private String _type;

    public ComponentSpecificationResolver(IRequestCycle cycle)
    {
        super(cycle);
    }

    protected void reset()
    {
        _type = null;

        super.reset();
    }

    /**
     *  Passed the namespace of a container (to resolve the type in)
     *  and the type to resolve, performs the processing.  A "bare type"
     *  (without a library prefix) may be in the containerNamespace,
     *  or the framework namespace
     *  (a search occurs in that order).
     * 
     *  @param cycle current request cycle
     *  @param containerNamespace namespace that may contain
     *  a library referenced in the type
     *  @param type the component specification
     *  to  find, either a simple name, or prefixed with a library id
     *  (defined for the container namespace)
     * 
     *  @see #getNamespace()
     *  @see #getSpecification()
     * 
     **/

    public void resolve(
        IRequestCycle cycle,
        INamespace containerNamespace,
        String type,
        ILocation location)
    {
        int colonx = type.indexOf(':');

        if (colonx > 0)
        {
            String libraryId = type.substring(0, colonx);
            String simpleType = type.substring(colonx + 1);

            resolve(cycle, containerNamespace, libraryId, simpleType, location);
        }
        else
            resolve(cycle, containerNamespace, null, type, location);
    }

    /**
     *  Like {@link #resolve(org.apache.tapestry.IRequestCycle, org.apache.tapestry.INamespace, java.lang.String, org.apache.tapestry.ILocation)},
     *  but used when the type has already been parsed into a library id and a simple type.
     * 
     *  @param cycle current request cycle
     *  @param containerNamespace namespace that may contain
     *  a library referenced in the type
     *  @param libraryId the library id within the container namespace, or null
     *  @param type the component specification
     *  to  find as a simple name (without a library prefix)
     *  @param location of reference to be resolved
     *  @throws ApplicationRuntimeException if the type cannot be resolved
     * 
     **/

    public void resolve(
        IRequestCycle cycle,
        INamespace containerNamespace,
        String libraryId,
        String type,
        ILocation location)
    {
        reset();
        _type = type;

        INamespace namespace = null;

        if (libraryId != null)
            namespace = containerNamespace.getChildNamespace(libraryId);
        else
            namespace = containerNamespace;

        setNamespace(namespace);

        if (namespace.containsComponentType(type))
            setSpecification(namespace.getComponentSpecification(type));
        else
            searchForComponent(cycle);

        // If not found after search, check to see if it's in
        // the framework instead.

        if (getSpecification() == null)
        {

            throw new ApplicationRuntimeException(
                Tapestry.format(
                    "Namespace.no-such-component-type",
                    type,
                    namespace.getNamespaceId()),
                location,
                null);

        }
    }

    private void searchForComponent(IRequestCycle cycle)
    {
        INamespace namespace = getNamespace();

        if (LOG.isDebugEnabled())
            LOG.debug("Resolving unknown component '" + _type + "' in " + namespace);

        String expectedName = _type + ".jwc";
        IResourceLocation namespaceLocation = namespace.getSpecificationLocation();

        // Look for appropriate file in same folder as the library (or application)
        // specificaiton.

        if (found(namespaceLocation.getRelativeLocation(expectedName)))
            return;

        if (namespace.isApplicationNamespace())
        {

            // The application namespace gets some extra searching.

            if (found(getWebInfAppLocation().getRelativeLocation(expectedName)))
                return;

            if (found(getWebInfLocation().getRelativeLocation(expectedName)))
                return;

            if (found(getApplicationRootLocation().getRelativeLocation(expectedName)))
                return;
        }

        // Not in the library or app spec; does it match a component
        // provided by the Framework?

        INamespace framework = getSpecificationSource().getFrameworkNamespace();

        if (framework.containsComponentType(_type))
        {
            setSpecification(framework.getComponentSpecification(_type));
            return;
        }

        IComponentSpecification specification =
            getDelegate().findComponentSpecification(cycle, namespace, _type);

        setSpecification(specification);

        // If not found by here, an exception will be thrown.
    }

    private boolean found(IResourceLocation location)
    {
        if (LOG.isDebugEnabled())
            LOG.debug("Checking: " + location);

        if (location.getResourceURL() == null)
            return false;

        setSpecification(getSpecificationSource().getComponentSpecification(location));

        install();

        return true;
    }

    private void install()
    {
        INamespace namespace = getNamespace();
        IComponentSpecification specification = getSpecification();

        if (LOG.isDebugEnabled())
            LOG.debug(
                "Installing component type "
                    + _type
                    + " into "
                    + namespace
                    + " as "
                    + specification);

        namespace.installComponentSpecification(_type, specification);
    }

}
