package org.codehaus.mojo.gwt;

/*
 * 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.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.apache.maven.plugin.MojoExecutionException;
import org.codehaus.mojo.gwt.utils.GwtModuleReaderException;
import org.codehaus.plexus.util.xml.Xpp3Dom;




/**
 * @author <a href="mailto:nicolas@apache.org">Nicolas De Loof</a>
 */
public class GwtModule
{
    private Xpp3Dom xml;

    private String name;

    private Set<GwtModule> inherits;

    private GwtModuleReader reader;
    
    private File sourceFile;

    public GwtModule( String name, Xpp3Dom xml, GwtModuleReader reader )
    {
        this.name = name;
        this.xml = xml;
        this.reader = reader;
    }

    private String getRenameTo()
    {
        return xml.getAttribute( "rename-to" );
    }

    public String getPublic()
    {
        Xpp3Dom node = xml.getChild( "public" );
        return ( node == null ? "public" : node.getAttribute( "path" ) );
    }

    public String[] getSuperSources()
    {
        Xpp3Dom nodes[] = xml.getChildren( "super-source" );
        if ( nodes == null )
        {
            return new String[0];
        }
        String[] superSources = new String[nodes.length];
        int i = 0;
        for ( Xpp3Dom node : nodes )
        {
            String path = node.getAttribute( "path" );
            if ( path == null )
            {
                path = "";
            }
            superSources[i++] = path;
        }
        return superSources;
    }

    public String[] getSources()
    {
        Xpp3Dom nodes[] = xml.getChildren( "source" );
        if ( nodes == null || nodes.length == 0 )
        {
            return new String[] { "client" };
        }
        String[] sources = new String[nodes.length];
        int i = 0;
        for ( Xpp3Dom node : nodes )
        {
            sources[i++] = node.getAttribute( "path" );
        }
        return sources;
    }

    public List<String> getEntryPoints()
        throws GwtModuleReaderException
    {
        List<String> entryPoints = new ArrayList<String>();
        entryPoints.addAll( getLocalEntryPoints() );
        for ( GwtModule module : getInherits() )
        {
            entryPoints.addAll( module.getLocalEntryPoints() );
        }
        return entryPoints;
    }

    private List<String> getLocalEntryPoints()
    {
        Xpp3Dom nodes[] = xml.getChildren( "entry-point" );
        if ( nodes == null )
        {
            return Collections.emptyList();
        }
        List<String> entryPoints = new ArrayList<String>( nodes.length );
        for ( Xpp3Dom node : nodes )
        {
            entryPoints.add( node.getAttribute( "class" ) );
        }
        return entryPoints;
    }

    /**
     * Build the set of inhertied modules. Due to xml inheritence mecanism, there may be cicles in the inheritence
     * graph, so we build a set of inherited modules
     */
    public Set<GwtModule> getInherits()
		throws GwtModuleReaderException
    {
        if ( inherits != null )
        {
            return inherits;
        }

        inherits = new HashSet<GwtModule>();
        addInheritedModules( inherits, getLocalInherits() );

        return inherits;
    }

    /**
     * 
     * @param set
     * @param modules
     * @throws MojoExecutionException
     */
    private void addInheritedModules( Set<GwtModule> set, Set<GwtModule> modules )
        throws GwtModuleReaderException
    {
        for ( GwtModule module : modules )
        {
            if ( set.add( module ) )
            {
                // if module is allready in the set, don't re-parse it's inherits
                addInheritedModules( set, module.getLocalInherits() );
            }
        }

    }

    private Set<GwtModule> getLocalInherits()
        throws GwtModuleReaderException
    {
        Xpp3Dom nodes[] = xml.getChildren( "inherits" );
        if ( nodes == null )
        {
            return Collections.emptySet();
        }
        Set<GwtModule> modules = new HashSet<GwtModule>();
        for ( Xpp3Dom node : nodes )
        {
            String moduleName = node.getAttribute( "name" );
            // exclude modules from gwt-dev/gwt-user
            if ( !moduleName.startsWith( "com.google.gwt." ) )
            {
                modules.add( reader.readModule( moduleName ) );
            }
        }
        return modules;
    }

    public Map<String, String> getServlets()
        throws GwtModuleReaderException
    {
        return getServlets( getPath() );
    }

    public Map<String, String> getServlets( String path )
        throws GwtModuleReaderException
    {
        Map<String, String> servlets = getLocalServlets( path );
        for ( GwtModule module : getInherits() )
        {
            servlets.putAll( module.getLocalServlets( path ) );
        }
        return servlets;
    }

    private Map<String, String> getLocalServlets( String path )
    {
        Map<String, String> servlets = new HashMap<String, String>();
        Xpp3Dom nodes[] = xml.getChildren( "servlet" );
        if ( nodes != null )
        {
            for ( Xpp3Dom node : nodes )
            {
                servlets.put( StringUtils.isBlank( path ) ? node.getAttribute( "path" ) : path + node.getAttribute( "path" ),
                              node.getAttribute( "class" ) );
            }
        }
        return servlets;
    }

    public String getName()
    {
        return name;
    }

    public String getPackage()
    {
        int index = name.lastIndexOf( '.' );
        return ( index < 0 ) ? "" : name.substring( 0, index );
    }

    public String getPath()
    {
        if ( getRenameTo() != null )
        {
            return getRenameTo();
        }
        return name;
    }

    public File getSourceFile() {
    	return sourceFile;
    }
    
    public void setSourceFile(File file) {
		this.sourceFile = file;
	}
	
    @Override
    public boolean equals( Object obj )
    {
        return name.equals( ( (GwtModule) obj ).name );
    }

    @Override
    public int hashCode()
    {
        return name.hashCode();
    }

}
