package org.apache.maven.exception;

/*
 * 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.IOException;
import java.net.ConnectException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;

import org.apache.maven.lifecycle.LifecycleExecutionException;
import org.apache.maven.model.building.ModelProblem;
import org.apache.maven.model.building.ModelProblemUtils;
import org.apache.maven.plugin.AbstractMojoExecutionException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugin.PluginContainerException;
import org.apache.maven.plugin.PluginExecutionException;
import org.apache.maven.project.ProjectBuildingException;
import org.apache.maven.project.ProjectBuildingResult;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.util.StringUtils;

/*

- test projects for each of these
- how to categorize the problems so that the id of the problem can be match to a page with descriptive help and the test
  project
- nice little sample projects that could be run in the core as well as integration tests

All Possible Errors
- invalid lifecycle phase (maybe same as bad CLI param, though you were talking about embedder too)
- <module> specified is not found
- malformed settings
- malformed POM
- local repository not writable
- remote repositories not available
- artifact metadata missing
- extension metadata missing
- extension artifact missing
- artifact metadata retrieval problem
- version range violation
- circular dependency
- artifact missing
- artifact retrieval exception
- md5 checksum doesn't match for local artifact, need to redownload this
- POM doesn't exist for a goal that requires one
- parent POM missing (in both the repository + relative path)
- component not found

Plugins:
- plugin metadata missing
- plugin metadata retrieval problem
- plugin artifact missing
- plugin artifact retrieval problem
- plugin dependency metadata missing
- plugin dependency metadata retrieval problem
- plugin configuration problem
- plugin execution failure due to something that is know to possibly go wrong (like compilation failure)
- plugin execution error due to something that is not expected to go wrong (the compiler executable missing)
- asking to use a plugin for which you do not have a version defined - tools to easily select versions
- goal not found in a plugin (probably could list the ones that are)

 */

// PluginNotFoundException, PluginResolutionException, PluginDescriptorParsingException,
// CycleDetectedInPluginGraphException;

/**
 * Transform an exception into useful end-user message.
 */
@Component( role = ExceptionHandler.class )
public class DefaultExceptionHandler
    implements ExceptionHandler
{

    public ExceptionSummary handleException( Throwable exception )
    {
        return handle( "", exception );
    }

    private ExceptionSummary handle( String message, Throwable exception )
    {
        String reference = getReference( exception );

        List<ExceptionSummary> children = null;

        if ( exception instanceof ProjectBuildingException )
        {
            List<ProjectBuildingResult> results = ( (ProjectBuildingException) exception ).getResults();

            children = new ArrayList<>();

            for ( ProjectBuildingResult result : results )
            {
                ExceptionSummary child = handle( result );
                if ( child != null )
                {
                    children.add( child );
                }
            }

            message = "The build could not read " + children.size() + " project" + ( children.size() == 1 ? "" : "s" );
        }
        else
        {
            message = getMessage( message, exception );
        }

        return new ExceptionSummary( exception, message, reference, children );
    }

    private ExceptionSummary handle( ProjectBuildingResult result )
    {
        List<ExceptionSummary> children = new ArrayList<>();

        for ( ModelProblem problem : result.getProblems() )
        {
            ExceptionSummary child = handle( problem, result.getProjectId() );
            if ( child != null )
            {
                children.add( child );
            }
        }

        if ( children.isEmpty() )
        {
            return null;
        }

        String message =
            "\nThe project " + result.getProjectId() + " (" + result.getPomFile() + ") has "
                + children.size() + " error" + ( children.size() == 1 ? "" : "s" );

        return new ExceptionSummary( null, message, null, children );
    }

    private ExceptionSummary handle( ModelProblem problem, String projectId )
    {
        if ( ModelProblem.Severity.ERROR.compareTo( problem.getSeverity() ) >= 0 )
        {
            String message = problem.getMessage();

            String location = ModelProblemUtils.formatLocation( problem, projectId );

            if ( StringUtils.isNotEmpty( location ) )
            {
                message += " @ " + location;
            }

            return handle( message, problem.getException() );
        }
        else
        {
            return null;
        }
    }

    private String getReference( Throwable exception )
    {
        String reference = "";

        if ( exception != null )
        {
            if ( exception instanceof MojoExecutionException )
            {
                reference = MojoExecutionException.class.getSimpleName();

                Throwable cause = exception.getCause();
                if ( cause instanceof IOException )
                {
                    cause = cause.getCause();
                    if ( cause instanceof ConnectException )
                    {
                        reference = ConnectException.class.getSimpleName();
                    }
                }
            }
            else if ( exception instanceof MojoFailureException )
            {
                reference = MojoFailureException.class.getSimpleName();
            }
            else if ( exception instanceof LinkageError )
            {
                reference = LinkageError.class.getSimpleName();
            }
            else if ( exception instanceof PluginExecutionException )
            {
                Throwable cause = exception.getCause();

                if ( cause instanceof PluginContainerException )
                {
                    Throwable cause2 = cause.getCause();

                    if ( cause2 instanceof NoClassDefFoundError )
                    {
                        String message = cause2.getMessage();
                        if ( message != null && message.contains( "org/sonatype/aether/" ) )
                        {
                            reference = "AetherClassNotFound";
                        }
                    }
                }

                if ( StringUtils.isEmpty( reference ) )
                {
                    reference = getReference( cause );
                }

                if ( StringUtils.isEmpty( reference ) )
                {
                    reference = exception.getClass().getSimpleName();
                }
            }
            else if ( exception instanceof LifecycleExecutionException )
            {
                reference = getReference( exception.getCause() );
            }
            else if ( isNoteworthyException( exception ) )
            {
                reference = exception.getClass().getSimpleName();
            }
        }

        if ( StringUtils.isNotEmpty( reference ) && !reference.startsWith( "http:" ) )
        {
            reference = "http://cwiki.apache.org/confluence/display/MAVEN/" + reference;
        }

        return reference;
    }

    private boolean isNoteworthyException( Throwable exception )
    {
        if ( exception == null )
        {
            return false;
        }
        else if ( exception instanceof Error )
        {
            return true;
        }
        else if ( exception instanceof RuntimeException )
        {
            return false;
        }
        else if ( exception.getClass().getName().startsWith( "java" ) )
        {
            return false;
        }
        return true;
    }

    private String getMessage( String message, Throwable exception )
    {
        String fullMessage = ( message != null ) ? message : "";

        for ( Throwable t = exception; t != null; t = t.getCause() )
        {
            String exceptionMessage = t.getMessage();

            if ( t instanceof AbstractMojoExecutionException )
            {
                String longMessage = ( (AbstractMojoExecutionException) t ).getLongMessage();
                if ( StringUtils.isNotEmpty( longMessage ) )
                {
                    if ( StringUtils.isEmpty( exceptionMessage ) || longMessage.contains( exceptionMessage ) )
                    {
                        exceptionMessage = longMessage;
                    }
                    else if ( !exceptionMessage.contains( longMessage ) )
                    {
                        exceptionMessage = join( exceptionMessage, '\n' + longMessage );
                    }
                }
            }

            if ( StringUtils.isEmpty( exceptionMessage ) )
            {
                exceptionMessage = t.getClass().getSimpleName();
            }

            if ( t instanceof UnknownHostException && !fullMessage.contains( "host" ) )
            {
                fullMessage = join( fullMessage, "Unknown host " + exceptionMessage );
            }
            else if ( !fullMessage.contains( exceptionMessage ) )
            {
                fullMessage = join( fullMessage, exceptionMessage );
            }
        }

        return fullMessage.trim();
    }

    private String join( String message1, String message2 )
    {
        String message = "";

        if ( StringUtils.isNotEmpty( message1 ) )
        {
            message = message1.trim();
        }

        if ( StringUtils.isNotEmpty( message2 ) )
        {
            if ( StringUtils.isNotEmpty( message ) )
            {
                if ( message.endsWith( "." ) || message.endsWith( "!" ) || message.endsWith( ":" ) )
                {
                    message += " ";
                }
                else
                {
                    message += ": ";
                }
            }

            message += message2;
        }

        return message;
    }

}
