package org.apache.maven.plugins.invoker;

/*
 * 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.Arrays;
import java.util.Properties;

import org.apache.maven.shared.invoker.InvocationRequest;
import org.apache.maven.shared.invoker.InvocationRequest.ReactorFailureBehavior;
import org.codehaus.plexus.util.StringUtils;

/**
 * Provides a convenient facade around the <code>invoker.properties</code>.
 *
 * @author Benjamin Bentmann
 * @version $Id: InvokerProperties.java 1779250 2017-01-17 20:20:02Z rfscholte $
 */
class InvokerProperties
{
    private static final String SELECTOR_PREFIX = "selector.";

    private enum InvocationProperty
    {
        PROJECT( "invoker.project" ),
        GOALS( "invoker.goals" ),
        PROFILES( "invoker.profiles" ),
        MAVEN_OPTS( "invoker.mavenOpts" ),
        FAILURE_BEHAVIOR( "invoker.failureBehavior" ),
        NON_RECURSIVE( "invoker.nonRecursive" ),
        OFFLINE( "invoker.offline" ),
        SYSTEM_PROPERTIES_FILE( "invoker.systemPropertiesFile" ),
        DEBUG( "invoker.debug" ),
        SETTINGS_FILE ( "invoker.settingsFile" );

        private final String key;

        private InvocationProperty( final String s )
        {
            this.key = s;
        }

        @Override
        public String toString()
        {
            return key;
        }
    }
    
    private enum SelectorProperty
    {
        JAVA_VERSION( ".java.version" ),
        MAVEN_VERSION( ".maven.version" ),
        OS_FAMLY( ".os.family" );
        
        private final String suffix;
        
        private SelectorProperty( String suffix )
        {
            this.suffix = suffix;
        }
        
        @Override
        public String toString()
        {
            return suffix;
        }
    }

    /**
     * The invoker properties being wrapped.
     */
    private final Properties properties;

    /**
     * Creates a new facade for the specified invoker properties. The properties will not be copied, so any changes to
     * them will be reflected by the facade.
     *
     * @param properties The invoker properties to wrap, may be <code>null</code> if none.
     */
    public InvokerProperties( Properties properties )
    {
        this.properties = ( properties != null ) ? properties : new Properties();
    }

    /**
     * Gets the invoker properties being wrapped.
     *
     * @return The invoker properties being wrapped, never <code>null</code>.
     */
    public Properties getProperties()
    {
        return this.properties;
    }

    /**
     * Gets the name of the corresponding build job.
     *
     * @return The name of the build job or an empty string if not set.
     */
    public String getJobName()
    {
        return this.properties.getProperty( "invoker.name", "" );
    }

    /**
     * Gets the description of the corresponding build job.
     *
     * @return The description of the build job or an empty string if not set.
     */
    public String getJobDescription()
    {
        return this.properties.getProperty( "invoker.description", "" );
    }

    /**
     * Gets the specification of JRE versions on which this build job should be run.
     *
     * @return The specification of JRE versions or an empty string if not set.
     */
    public String getJreVersion()
    {
        return this.properties.getProperty( "invoker.java.version", "" );
    }

    /**
     * Gets the specification of JRE versions on which this build job should be run.
     *
     * @return The specification of JRE versions or an empty string if not set.
     */
    public String getJreVersion( int index )
    {
        return this.properties.getProperty( SELECTOR_PREFIX + index + SelectorProperty.JAVA_VERSION.suffix,
                                            getJreVersion() );
    }

    /**
     * Gets the specification of Maven versions on which this build job should be run.
     *
     * @return The specification of Maven versions on which this build job should be run.
     * @since 1.5
     */
    public String getMavenVersion()
    {
        return this.properties.getProperty( "invoker.maven.version", "" );
    }
    
    /**
     * 
     * @param index the selector index
     * @return The specification of Maven versions on which this build job should be run.
     * @since 3.0.0
     */
    public String getMavenVersion( int index )
    {
        return this.properties.getProperty( SELECTOR_PREFIX + index + SelectorProperty.MAVEN_VERSION.suffix,
                                            getMavenVersion() );
    }

    /**
     * Gets the specification of OS families on which this build job should be run.
     *
     * @return The specification of OS families or an empty string if not set.
     */
    public String getOsFamily()
    {
        return this.properties.getProperty( "invoker.os.family", "" );
    }
    
    /**
     * Gets the specification of OS families on which this build job should be run.
     *
     * @param index the selector index
     * @return The specification of OS families or an empty string if not set.
     * @since 3.0.0
     */
    public String getOsFamily( int index )
    {
        return this.properties.getProperty( SELECTOR_PREFIX + index + SelectorProperty.OS_FAMLY.suffix,
                                            getOsFamily() );
    }   
    

    /**
     * Determines whether these invoker properties contain a build definition for the specified invocation index.
     *
     * @param index The one-based index of the invocation to check for, must not be negative.
     * @return <code>true</code> if the invocation with the specified index is defined, <code>false</code> otherwise.
     */
    public boolean isInvocationDefined( int index )
    {
        for ( InvocationProperty prop : InvocationProperty.values() )
        {
            if ( properties.getProperty( prop.toString() + '.' + index ) != null )
            {
                return true;
            }
        }
        return false;
    }
    
    /**
     * Determines whether these invoker properties contain a build definition for the specified selector index.
     * 
     * @param index the index
     * @return <code>true</code> if the selector with the specified index is defined, <code>false</code> otherwise.
     * @since 3.0.0
     */
    public boolean isSelectorDefined( int index )
    {
        for ( SelectorProperty prop : SelectorProperty.values() )
        {
            if ( properties.getProperty( SELECTOR_PREFIX + index + prop.suffix ) != null )
            {
                return true;
            }
        }
        return false;
    }

    /**
     * Configures the specified invocation request from these invoker properties. Settings not present in the invoker
     * properties will be left unchanged in the invocation request.
     *
     * @param request The invocation request to configure, must not be <code>null</code>.
     * @param index The one-based index of the invocation to configure, must not be negative.
     */
    public void configureInvocation( InvocationRequest request, int index )
    {
        String project = get( InvocationProperty.PROJECT, index );
        if ( project != null )
        {
            File file = new File( request.getBaseDirectory(), project );
            if ( file.isFile() )
            {
                request.setBaseDirectory( file.getParentFile() );
                request.setPomFile( file );
            }
            else
            {
                request.setBaseDirectory( file );
                request.setPomFile( null );
            }
        }

        String goals = get( InvocationProperty.GOALS, index );
        if ( goals != null )
        {
            request.setGoals( new ArrayList<String>( Arrays.asList( StringUtils.split( goals, ", \t\n\r\f" ) ) ) );
        }

        String profiles = get( InvocationProperty.PROFILES, index );
        if ( profiles != null )
        {
            // CHECKSTYLE_OFF: LineLength
            request.setProfiles( new ArrayList<String>( Arrays.asList( StringUtils.split( profiles,
                                                                                          ", \t\n\r\f" ) ) ) );
            // CHECKSTYLE_ON: LineLength
        }

        String mvnOpts = get( InvocationProperty.MAVEN_OPTS, index );
        if ( mvnOpts != null )
        {
            request.setMavenOpts( mvnOpts );
        }

        String failureBehavior = get( InvocationProperty.FAILURE_BEHAVIOR, index );
        if ( failureBehavior != null )
        {
            ReactorFailureBehavior valueOf =
                InvocationRequest.ReactorFailureBehavior.valueOfByLongOption( failureBehavior );
            request.setReactorFailureBehavior( valueOf );
        }

        String nonRecursive = get( InvocationProperty.NON_RECURSIVE, index );
        if ( nonRecursive != null )
        {
            request.setRecursive( !Boolean.valueOf( nonRecursive ) );
        }

        String offline = get( InvocationProperty.OFFLINE, index );
        if ( offline != null )
        {
            request.setOffline( Boolean.valueOf( offline ) );
        }

        String debug = get( InvocationProperty.DEBUG, index );
        if ( debug != null )
        {
            request.setDebug( Boolean.valueOf( debug ) );
        }
    }

    /**
     * Checks whether the specified exit code matches the one expected for the given invocation.
     *
     * @param exitCode The exit code of the Maven invocation to check.
     * @param index The index of the invocation for which to check the exit code, must not be negative.
     * @return <code>true</code> if the exit code is zero and a success was expected or if the exit code is non-zero and
     *         a failue was expected, <code>false</code> otherwise.
     */
    public boolean isExpectedResult( int exitCode, int index )
    {
        boolean nonZeroExit = "failure".equalsIgnoreCase( get( "invoker.buildResult", index ) );
        return ( exitCode != 0 ) == nonZeroExit;
    }

    /**
     * Gets the path to the properties file used to set the system properties for the specified invocation.
     *
     * @param index The index of the invocation, must not be negative.
     * @return The path to the properties file or <code>null</code> if not set.
     */
    public String getSystemPropertiesFile( int index )
    {
        return get( InvocationProperty.SYSTEM_PROPERTIES_FILE, index );
    }

    /**
     * Gets the settings file used for the specified invocation.
     * 
     * @param index The index of the invocation, must not be negative.
     * @return the value for the settings file or <code>null</code> if not set.
     */
    public String getSettingsFile( int index )
    {
        return get( InvocationProperty.SETTINGS_FILE, index );
    }

    /**
     * Gets a value from the invoker properties. The invoker properties are intended to describe the invocation settings
     * for multiple builds of the same project. For this reason, the properties are indexed. First, a property named
     * <code>key.index</code> will be queried. If this property does not exist, the value of the property named
     * <code>key</code> will finally be returned.
     *
     * @param key The (base) key for the invoker property to lookup, must not be <code>null</code>.
     * @param index The index of the invocation for which to retrieve the value, must not be negative.
     * @return The value for the requested invoker property or <code>null</code> if not defined.
     */
    String get( String key, int index )
    {
        if ( index < 0 )
        {
            throw new IllegalArgumentException( "invalid invocation index: " + index );
        }

        String value = properties.getProperty( key + '.' + index );
        if ( value == null )
        {
            value = properties.getProperty( key );
        }
        return value;
    }

    private String get( InvocationProperty prop, int index )
    {
        return get( prop.toString(), index );
    }
}
