/*******************************************************************************
 * 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.plexus;

import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import junit.framework.TestCase;

import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.eclipse.sisu.bean.BeanProperty;
import org.eclipse.sisu.inject.DeferredClass;
import org.eclipse.sisu.inject.DeferredProvider;
import org.eclipse.sisu.space.ClassSpace;
import org.eclipse.sisu.space.LoadedClass;
import org.eclipse.sisu.space.URLClassSpace;
import org.eclipse.sisu.space.asm.ClassWriter;
import org.eclipse.sisu.space.asm.MethodVisitor;
import org.eclipse.sisu.space.asm.Opcodes;

import com.google.inject.TypeLiteral;

public class PlexusXmlScannerTest
    extends TestCase
{
    static class NamedProperty
        implements BeanProperty<Object>
    {
        final String name;

        final TypeLiteral<Object> type;

        public NamedProperty( final String name )
        {
            this.name = name;
            type = TypeLiteral.get( Object.class );
        }

        @SuppressWarnings( { "unchecked", "rawtypes" } )
        public NamedProperty( final String name, final TypeLiteral type )
        {
            this.name = name;
            this.type = type;
        }

        public <A extends Annotation> A getAnnotation( final Class<A> annotationType )
        {
            return null;
        }

        public String getName()
        {
            return name;
        }

        public TypeLiteral<Object> getType()
        {
            return type;
        }

        public <B> void set( final B bean, final Object value )
        {
        }
    }

    interface Bean
    {
    }

    protected static class DefaultBean
    {
    }

    static class DebugBean
    {
    }

    static class AnotherBean
    {
    }

    static class EmptyClassSpace
        implements ClassSpace
    {
        public Class<?> loadClass( final String name )
        {
            try
            {
                return Class.forName( name );
            }
            catch ( final ClassNotFoundException e )
            {
                throw new TypeNotPresentException( name, e );
            }
        }

        @SuppressWarnings( { "unchecked", "rawtypes" } )
        public DeferredClass<?> deferLoadClass( final String name )
        {
            return new LoadedClass( loadClass( name ) );
        }

        public URL getResource( final String name )
        {
            return null;
        }

        public Enumeration<URL> getResources( final String name )
        {
            // hide components.xml so we can just test plexus.xml parsing
            return Collections.enumeration( Collections.<URL> emptyList() );
        }

        public Enumeration<URL> findEntries( final String path, final String glob, final boolean recurse )
        {
            // hide components.xml so we can just test plexus.xml parsing
            return Collections.enumeration( Collections.<URL> emptyList() );
        }
    }

    public void testLoadOnStart()
    {
        final URL plexusXml = getClass().getResource( "/META-INF/plexus/plexus.xml" );
        final PlexusXmlScanner scanner = new PlexusXmlScanner( null, plexusXml, null );

        final Map<Component, DeferredClass<?>> componentMap = scanner.scan( new EmptyClassSpace(), true );

        assertEquals( 2, componentMap.size() );

        final Component component1 =
            new ComponentImpl( DefaultBean.class, Hints.DEFAULT_HINT, Strategies.LOAD_ON_START, "" );
        assertEquals( DefaultBean.class, componentMap.get( component1 ).load() );

        final Component component2 = new ComponentImpl( Bean.class, "debug", Strategies.LOAD_ON_START, "For debugging" );
        assertEquals( DebugBean.class, componentMap.get( component2 ).load() );
    }

    public void testBadPlexusXml()
    {
        final ClassSpace space = new URLClassSpace( PlexusXmlScannerTest.class.getClassLoader() );
        final URL plexusXml = getClass().getResource( "/META-INF/plexus/bad_plexus_1.xml" );
        new PlexusXmlScanner( null, plexusXml, null ).scan( space, true );
    }

    static class FixedClassSpace
        implements ClassSpace
    {
        final String fixedResourceName;

        FixedClassSpace( final String fixedResourceName )
        {
            this.fixedResourceName = fixedResourceName;
        }

        public Class<?> loadClass( final String name )
        {
            try
            {
                return Class.forName( name );
            }
            catch ( final ClassNotFoundException e )
            {
                throw new TypeNotPresentException( name, e );
            }
        }

        @SuppressWarnings( "rawtypes" )
        public DeferredClass<?> deferLoadClass( final String name )
        {
            return new DeferredClass()
            {
                public Class load()
                {
                    return loadClass( name );
                }

                public String getName()
                {
                    return name;
                }

                public DeferredProvider asProvider()
                {
                    throw new UnsupportedOperationException();
                }
            };
        }

        public URL getResource( final String name )
        {
            return getClass().getResource( fixedResourceName );
        }

        public Enumeration<URL> getResources( final String name )
        {
            return Collections.enumeration( Collections.singleton( getClass().getResource( fixedResourceName ) ) );
        }

        public Enumeration<URL> findEntries( final String path, final String glob, final boolean recurse )
        {
            return Collections.enumeration( Collections.singleton( getClass().getResource( fixedResourceName ) ) );
        }
    }

    public void testBadComponentsXml()
    {
        ClassSpace space;

        space = new FixedClassSpace( "/META-INF/plexus/bad_components_1.xml" );
        new PlexusXmlScanner( null, null, null ).scan( space, true );

        space = new FixedClassSpace( "/META-INF/plexus/bad_components_2.xml" );
        new PlexusXmlScanner( null, null, null ).scan( space, true );

        try
        {
            space = new FixedClassSpace( "/META-INF/plexus/bad_components_3.xml" );
            final Map<String, PlexusBeanMetadata> metadata = new HashMap<String, PlexusBeanMetadata>();
            final PlexusXmlScanner scanner = new PlexusXmlScanner( null, null, metadata );

            scanner.scan( space, true );

            final Requirement badReq =
                metadata.get( DefaultBean.class.getName() ).getRequirement( new NamedProperty( "no.such.class" ) );

            badReq.role();
            fail( "Expected TypeNotPresentException" );
        }
        catch ( final TypeNotPresentException e )
        {
        }

        space = new FixedClassSpace( "/META-INF/plexus/bad_components_4.xml" );
        final PlexusXmlScanner scanner = new PlexusXmlScanner( null, null, null );
        assertTrue( scanner.scan( space, true ).isEmpty() );
    }

    public void testInterpolatedComponentsXml()
    {
        final ClassSpace space = new FixedClassSpace( "/META-INF/plexus/variable_components.xml" );

        final Map<String, PlexusBeanMetadata> metadata = new HashMap<String, PlexusBeanMetadata>();

        new PlexusXmlScanner( null, null, metadata ).scan( space, true );

        assertEquals( "${some.value}",
                      metadata.get( DefaultBean.class.getName() ).getConfiguration( new NamedProperty( "variable" ) ).value() );

        final Map<?, ?> variables = Collections.singletonMap( "some.value", "INTERPOLATED" );

        new PlexusXmlScanner( variables, null, metadata ).scan( space, true );

        assertEquals( "INTERPOLATED",
                      metadata.get( DefaultBean.class.getName() ).getConfiguration( new NamedProperty( "variable" ) ).value() );
    }

    public void testLocalizedXmlScanning()
    {
        final ClassSpace space = new URLClassSpace( PlexusXmlScannerTest.class.getClassLoader(), null );

        assertFalse( new PlexusXmlScanner( null, null, null ).scan( space, true ).isEmpty() );
        assertTrue( new PlexusXmlScanner( null, null, null ).scan( space, false ).isEmpty() );
    }

    public void testOptionalLogging()
        throws Exception
    {
        final Level level = Logger.getLogger( "" ).getLevel();
        try
        {
            Logger.getLogger( "" ).setLevel( Level.SEVERE );

            // check everything still works without any SLF4J jars
            final ClassLoader noLoggingLoader =
                new URLClassLoader( new URLClassSpace( getClass().getClassLoader() ).getURLs(), null )
                {
                    @Override
                    protected synchronized Class<?> loadClass( final String name, final boolean resolve )
                        throws ClassNotFoundException
                    {
                        if ( name.contains( "slf4j" ) )
                        {
                            throw new ClassNotFoundException( name );
                        }
                        if ( name.contains( "cobertura" ) )
                        {
                            return PlexusXmlScannerTest.class.getClassLoader().loadClass( name );
                        }
                        return super.loadClass( name, resolve );
                    }
                };

            noLoggingLoader.loadClass( SimpleScanningExample.class.getName() ).newInstance();
        }
        finally
        {
            Logger.getLogger( "" ).setLevel( level );
        }
    }

}
