package play.modules.spring;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.Set;

import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.classreading.CachingMetadataReaderFactory;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;

import play.Logger;
import play.Play;
import play.classloading.ApplicationClasses.ApplicationClass;

public class PlayClassPathBeanDefinitionScanner extends ClassPathBeanDefinitionScanner
{
	private ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();

	private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(
		this .resourcePatternResolver);

	/**
	 * The constructor, which just passed on to the parent class.
	 *
	 * @param registry
	 */
	public PlayClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry)
	{
		super(registry);
	}

	/**
	 * The override, which searches through the play framework's classes, instead of using
	 * files (as Spring is trained to do).
	 */
	@Override
	public Set<BeanDefinition> findCandidateComponents(String basePackage)
	{
		Logger.debug("Finding candidate components with base package: " + basePackage);

		Set<BeanDefinition> candidates = new LinkedHashSet<BeanDefinition>();

		try
		{
			for (ApplicationClass appClass : Play.classes.all())
			{
				if (appClass.name.startsWith(basePackage))
				{
					Logger.debug("Scanning class: " + appClass.name);

                    AbstractResource res = null;
                    if (Play.usePrecompiled)  {
                       File f = Play.getFile("precompiled/java/" + (appClass.name.replace(".", "/")) + ".class");
                       res = new InputStreamResource(new FileInputStream(f));
                    } else {
                       res = new ByteArrayResource(appClass.enhance());
                    }
    				MetadataReader metadataReader = this.metadataReaderFactory.getMetadataReader(res);

    				if (isCandidateComponent(metadataReader))
    				{
    					ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);
    					sbd.setSource(res);
    					if (isCandidateComponent(sbd))
    					{
    						candidates.add(sbd);
    					}
    				}
				}
				else
				{
					Logger.trace("Skipped class: " + appClass.name + " -- wrong base package");
				}
			}
		}
		catch (IOException ex)
		{
			throw new BeanDefinitionStoreException(
					"I/O failure during classpath scanning", ex);
		}
		return candidates;
	}
}
