/*******************************************************************************
 * Copyright (c) 2010, 2015 Sonatype, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    Stuart McCulloch (Sonatype, Inc.) - initial API and implementation
 *******************************************************************************/
package org.eclipse.sisu.space;

import java.net.URL;

import javax.inject.Qualifier;

import org.eclipse.sisu.inject.Logs;

import com.google.inject.Module;

/**
 * {@link SpaceVisitor} that reports types annotated with {@link Qualifier} annotations.
 */
public final class QualifiedTypeVisitor
    implements SpaceVisitor, ClassVisitor
{
    // ----------------------------------------------------------------------
    // Implementation fields
    // ----------------------------------------------------------------------

    private final QualifierCache qualifierCache = new QualifierCache();

    private final QualifiedTypeListener listener;

    private ClassSpace space;

    private URL location;

    private String source;

    private String clazzName;

    private boolean qualified;

    // ----------------------------------------------------------------------
    // Constructors
    // ----------------------------------------------------------------------

    public QualifiedTypeVisitor( final QualifiedTypeListener listener )
    {
        this.listener = listener;
    }

    // ----------------------------------------------------------------------
    // Public methods
    // ----------------------------------------------------------------------

    public static boolean verify( final ClassSpace space, final Class<?>... specification )
    {
        for ( final Class<?> expectedClazz : specification )
        {
            try
            {
                final Class<?> spaceClazz = space.loadClass( expectedClazz.getName() );
                if ( spaceClazz != expectedClazz )
                {
                    Logs.warn( "Inconsistent ClassLoader for: {} in: {}", expectedClazz, space );
                    Logs.warn( "Expected: {} saw: {}", expectedClazz.getClassLoader(), spaceClazz.getClassLoader() );
                }
            }
            catch ( final TypeNotPresentException e )
            {
                if ( expectedClazz.isAnnotation() )
                {
                    Logs.trace( "Potential problem: {} is not visible from: {}", expectedClazz, space );
                }
            }
        }
        return true;
    }

    public void enterSpace( final ClassSpace _space )
    {
        space = _space;
        source = null;

        if ( Logs.TRACE_ENABLED )
        {
            verify( _space, Qualifier.class, Module.class );
        }
    }

    public ClassVisitor visitClass( final URL url )
    {
        location = url;
        clazzName = null;
        qualified = false;

        return this;
    }

    public void enterClass( final int modifiers, final String name, final String _extends, final String[] _implements )
    {
        if ( ( modifiers & NON_INSTANTIABLE ) == 0 )
        {
            clazzName = name; // concrete type
        }
    }

    public AnnotationVisitor visitAnnotation( final String desc )
    {
        if ( null != clazzName )
        {
            qualified = qualified || qualifierCache.qualify( space, desc );
        }
        return null;
    }

    public void disqualify()
    {
        qualified = false;
    }

    public void leaveClass()
    {
        if ( qualified )
        {
            listener.hear( space.loadClass( clazzName.replace( '/', '.' ) ), findSource() );
        }
    }

    public void leaveSpace()
    {
        // no-op
    }

    // ----------------------------------------------------------------------
    // Implementation methods
    // ----------------------------------------------------------------------

    /**
     * Finds source of current class; detailed location or {@link ClassSpace#toString()}.
     */
    private String findSource()
    {
        if ( null != location )
        {
            // compressed record of class location
            final String path = location.getPath();
            if ( null == source || !path.startsWith( source ) )
            {
                final int i = path.indexOf( clazzName );
                source = i <= 0 ? path : path.substring( 0, i );
            }
        }
        else if ( null == source )
        {
            source = space.toString();
        }
        return source;
    }
}
