/*******************************************************************************
 * 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.reflect.Type;
import java.util.List;
import java.util.Map;

import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.eclipse.sisu.inject.TypeArguments;

import com.google.inject.Key;
import com.google.inject.ProvisionException;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;

/**
 * Utility methods for dealing with Plexus roles.
 */
public final class Roles
{
    // ----------------------------------------------------------------------
    // Constants
    // ----------------------------------------------------------------------

    private static final String MISSING_COMPONENT_ERROR = "No implementation for %s was bound.";

    private static final String MISSING_COMPONENT_WITH_HINT_ERROR =
        "No implementation for %s annotated with @Named(value=%s) was bound.";

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

    private Roles()
    {
        // static utility class, not allowed to create instances
    }

    // ----------------------------------------------------------------------
    // Utility methods
    // ----------------------------------------------------------------------

    /**
     * Returns the canonical form of the given Plexus role-hint.
     * 
     * @param role The Plexus role
     * @param hint The Plexus hint
     * @return Canonical role-hint denoting the same component as the given role-hint
     */
    public static String canonicalRoleHint( final String role, final String hint )
    {
        return Hints.isDefaultHint( hint ) ? role : role + ':' + hint;
    }

    /**
     * Returns the canonical role-hint for the given Plexus component.
     * 
     * @param component The Plexus component
     * @return Canonical role-hint denoting the given component
     */
    public static String canonicalRoleHint( final Component component )
    {
        return canonicalRoleHint( component.role().getName(), component.hint() );
    }

    /**
     * Deduces the role type based on the given @{@link Requirement} and expected type.
     * 
     * @param requirement The Plexus requirement
     * @param asType The expected type
     * @return "Best-fit" role type
     */
    public static TypeLiteral<?> roleType( final Requirement requirement, final TypeLiteral<?> asType )
    {
        final Type role = requirement.role();
        if ( role != Object.class && role != Map.class && role != List.class )
        {
            return TypeLiteral.get( role );
        }
        final Class<?> rawType = asType.getRawType();
        if ( Map.class == rawType )
        {
            // Map<String, T> --> T
            return TypeArguments.get( asType, 1 );
        }
        if ( List.class == rawType )
        {
            // List<T> --> T
            return TypeArguments.get( asType, 0 );
        }
        return asType;
    }

    /**
     * Returns the component binding {@link Key} for the given Plexus component.
     * 
     * @param component The Plexus component
     * @return Component binding key denoting the given component
     */
    public static Key<?> componentKey( final Component component )
    {
        return componentKey( component.role(), component.hint() );
    }

    /**
     * Returns the component binding {@link Key} for the given Plexus role-hint.
     * 
     * @param role The Plexus role
     * @param hint The Plexus hint
     * @return Component binding key denoting the given role-hint
     */
    public static <T> Key<T> componentKey( final Class<T> role, final String hint )
    {
        if ( Hints.isDefaultHint( hint ) )
        {
            return Key.get( role );
        }
        return Key.get( role, Names.named( Hints.canonicalHint( hint ) ) );
    }

    /**
     * Returns the component binding {@link Key} for the given Plexus role-hint.
     * 
     * @param role The Plexus role
     * @param hint The Plexus hint
     * @return Component binding key denoting the given role-hint
     */
    @SuppressWarnings( "unchecked" )
    public static <T> Key<T> componentKey( final TypeLiteral<T> role, final String hint )
    {
        return (Key<T>) componentKey( role.getRawType(), hint );
    }

    /**
     * Throws a {@link ProvisionException} detailing the missing Plexus component.
     * 
     * @param role The Plexus role
     * @param hint The Plexus hint
     */
    public static <T> T throwMissingComponentException( final TypeLiteral<T> role, final String hint )
    {
        if ( Hints.isDefaultHint( hint ) )
        {
            throw new ProvisionException( String.format( MISSING_COMPONENT_ERROR, role ) );
        }
        throw new ProvisionException( String.format( MISSING_COMPONENT_WITH_HINT_ERROR, role, hint ) );
    }

    /**
     * Removes any dashes from the name and converts it to camelCase.
     * 
     * @param name The element name
     * @return CamelCased name with no dashes
     */
    public static String camelizeName( final String name )
    {
        StringBuilder buf = null;

        final int length = name.length();
        for ( int i = 0; i < length; i++ )
        {
            if ( '-' == name.charAt( i ) )
            {
                buf = new StringBuilder( name.substring( 0, i ) );
                break;
            }
        }

        if ( null == buf )
        {
            return name; // nothing to camelize
        }

        boolean capitalize = true;
        for ( int i = buf.length() + 1; i < length; i++ )
        {
            final char c = name.charAt( i );
            if ( '-' == c )
            {
                capitalize = true;
            }
            else if ( capitalize )
            {
                buf.append( Character.toTitleCase( c ) );
                capitalize = false;
            }
            else
            {
                buf.append( c );
            }
        }

        return buf.toString();
    }
}
