package org.codehaus.mojo.gwt.utils;

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
 * agreements. See the NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The ASF licenses this file to you 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.
 */

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.model.Resource;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.project.MavenProject;
import org.codehaus.mojo.gwt.AbstractGwtModuleMojo;
import org.codehaus.mojo.gwt.ClasspathBuilder;
import org.codehaus.mojo.gwt.ClasspathBuilderException;
import org.codehaus.mojo.gwt.GwtModule;
import org.codehaus.mojo.gwt.GwtModuleReader;
import org.codehaus.plexus.util.DirectoryScanner;
import org.codehaus.plexus.util.ReaderFactory;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;

/**
 * @author <a href="mailto:olamy@apache.org">Olivier Lamy</a>
 * @since 2.1.0-1
 */
public class DefaultGwtModuleReader
    implements GwtModuleReader
{
    public static final String GWT_MODULE_EXTENSION = ".gwt.xml";

    private MavenProject mavenProject;

    private ClasspathBuilder classpathBuilder;

    private Log log;

    public DefaultGwtModuleReader( MavenProject mavenProject, Log log, ClasspathBuilder classpathBuilder )
    {
        this.mavenProject = mavenProject;
        this.log = log;
        this.classpathBuilder = classpathBuilder;
    }

    @SuppressWarnings("unchecked")
    public List<String> getGwtModules()
    {
        //Use a Set to avoid duplicate when user set src/main/java as <resource>
        Set<String> mods = new HashSet<String>();

        Collection<String> sourcePaths = (Collection<String>) mavenProject.getCompileSourceRoots();
        for ( String sourcePath : sourcePaths )
        {
            File sourceDirectory = new File( sourcePath );
            if ( sourceDirectory.exists() )
            {
                DirectoryScanner scanner = new DirectoryScanner();
                scanner.setBasedir( sourceDirectory.getAbsolutePath() );
                scanner.setIncludes( new String[] { "**/*" + GWT_MODULE_EXTENSION } );
                scanner.scan();

                mods.addAll( Arrays.asList( scanner.getIncludedFiles() ) );
            }
        }

        Collection<Resource> resources = (Collection<Resource>) mavenProject.getResources();
        for ( Resource resource : resources )
        {
            File resourceDirectoryFile = new File( resource.getDirectory() );
            if ( !resourceDirectoryFile.exists() )
            {
                continue;
            }
            DirectoryScanner scanner = new DirectoryScanner();
            scanner.setBasedir( resource.getDirectory() );
            scanner.setIncludes( new String[] { "**/*" + GWT_MODULE_EXTENSION } );
            scanner.scan();
            mods.addAll( Arrays.asList( scanner.getIncludedFiles() ) );
        }

        if ( mods.isEmpty() )
        {
            log.warn( "GWT plugin is configured to detect modules, but none were found." );
        }

        List<String> modules = new ArrayList<String>( mods.size() );
        for ( String fileName : mods )
        {
            String path = fileName.substring( 0, fileName.length() - GWT_MODULE_EXTENSION.length() );
            modules.add( path.replace( File.separatorChar, '.' ) );
        }
        if ( modules.size() > 0 )
        {
            log.info( "auto discovered modules " + modules );
        }
        return modules;
    }

    public GwtModule readModule( String name )
        throws GwtModuleReaderException
    {
        String modulePath = name.replace( '.', '/' ) + GWT_MODULE_EXTENSION;
        Collection<String> sourceRoots = mavenProject.getCompileSourceRoots();
        for ( String sourceRoot : sourceRoots )
        {
            File root = new File( sourceRoot );
            File xml = new File( root, modulePath );
            if ( xml.exists() )
            {
                log.debug( "GWT module " + name + " found in " + root );
                return readModule( name, xml );
            }
        }
        Collection<Resource> resources = (Collection<Resource>) mavenProject.getResources();
        for ( Resource resource : resources )
        {
            File root = new File( resource.getDirectory() );
            File xml = new File( root, modulePath );
            if ( xml.exists() )
            {
                log.debug( "GWT module " + name + " found in " + root );
                return readModule( name, xml );
            }
        }

        try
        {
            Collection<File> classpath = getClasspath( Artifact.SCOPE_COMPILE );
            URL[] urls = new URL[classpath.size()];
            int i = 0;
            for ( File file : classpath )
            {
                urls[i++] = file.toURI().toURL();
            }
            InputStream stream = new URLClassLoader( urls ).getResourceAsStream( modulePath );
            if ( stream != null )
            {
                return readModule( name, stream );
            }
        }
        catch ( MalformedURLException e )
        {
            // ignored;
        }
        catch ( ClasspathBuilderException e )
        {
            throw new GwtModuleReaderException( e.getMessage(), e );
        }

        throw new GwtModuleReaderException( "GWT Module " + name + " not found in project sources or resources." );
    }

    private GwtModule readModule( String name, File file )
        throws GwtModuleReaderException

    {
        try
        {
            return readModule( name, new FileInputStream( file ) );
        }
        catch ( FileNotFoundException e )
        {
            throw new GwtModuleReaderException( "Failed to read module file " + file );
        }
    }

    private GwtModule readModule( String name, InputStream xml )
        throws GwtModuleReaderException
    {
        try
        {
            Xpp3Dom dom = Xpp3DomBuilder.build( ReaderFactory.newXmlReader( xml ) );
            return new GwtModule( name, dom, this );
        }
        catch ( Exception e )
        {
            String error = "Failed to read module XML file " + xml;
            log.error( error );
            throw new GwtModuleReaderException( error, e );
        }
    }

    public Collection<File> getClasspath( String scope )
        throws ClasspathBuilderException
    {
        Collection<File> files = classpathBuilder.buildClasspathList( mavenProject, scope, mavenProject.getArtifacts(), false );

        if ( log.isDebugEnabled() )
        {
            log.debug( "GWT SDK execution classpath :" );
            for ( File f : files )
            {
                log.debug( "   " + f.getAbsolutePath() );
            }
        }
        return files;
    }
}
