package org.codehaus.mojo.animal_sniffer.ant;

/*
 * The MIT License
 *
 * Copyright (c) 2009, codehaus.org.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Vector;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.types.resources.FileResource;
import org.codehaus.mojo.animal_sniffer.ClassFileVisitor;
import org.codehaus.mojo.animal_sniffer.ClassListBuilder;
import org.codehaus.mojo.animal_sniffer.SignatureChecker;

/**
 * Checks the files against the provided signature.
 *
 * @author connollys
 * @since 1.3
 */
public class CheckSignatureTask
    extends Task
{

    private File signature;

    private Path classpath;

    private Path sourcepath;

    private boolean failOnError = true;

    private Vector<Path> paths = new Vector<>();

    private Vector<Ignore> ignores = new Vector<>();

    private Vector<Annotation> annotations = new Vector<>();

    public void addPath( Path path )
    {
        paths.add( path );
    }

    public Ignore createIgnore( )
    {
        final Ignore result = new Ignore();
        ignores.add( result );
        return result;
    }

    public Annotation createAnnotation()
    {
        final Annotation result = new Annotation();
        annotations.add( result );
        return result;
    }

    public void setSignature( File signature )
    {
        this.signature = signature;
    }

    public Path createClasspath()
    {
        log( "In createClasspath", Project.MSG_INFO );
        if ( this.classpath == null )
        {
            this.classpath = new Path( getProject() );
        }
        return this.classpath.createPath();
    }

    public void setClasspath( Path classpath )
    {
        log( "In setClasspath", Project.MSG_INFO );
        if ( this.classpath == null )
        {
            this.classpath = classpath;
        }
        else
        {
            this.classpath.append( classpath );
        }
    }

    public void setClasspathRef( Reference r )
    {
        log( "In setClasspathRef", Project.MSG_INFO );
        createClasspath().setRefid( r );
    }

    public Path createSourcepath()
    {
        log( "In createSourcepath", Project.MSG_INFO );
        if ( this.sourcepath == null )
        {
            this.sourcepath = new Path( getProject() );
        }
        return this.sourcepath.createPath();
    }

    public void setSourcepath( Path sourcepath )
    {
        log( "In setSourcepath", Project.MSG_INFO );
        if ( this.sourcepath == null )
        {
            this.sourcepath = sourcepath;
        }
        else
        {
            this.sourcepath.append( sourcepath );
        }
    }

    public void setSourcepathRef( Reference r )
    {
        log( "In setSourcepathRef", Project.MSG_INFO );
        createSourcepath().setRefid( r );
    }

    public void setFailOnError( boolean failOnError )
    {
        log( "In setFailOnError", Project.MSG_INFO );
        this.failOnError = failOnError;
    }

    public void execute()
        throws BuildException
    {
        validate();
        try
        {
            log( "Checking unresolved references to " + signature, Project.MSG_INFO );

            if ( !signature.isFile() )
            {
                throw new BuildException( "Could not find signature: " + signature );
            }

            final Set<String> ignoredPackages = buildPackageList();

            for ( Ignore ignore: ignores )
            {
                if ( ignore == null || ignore.getClassName() == null )
                {
                    continue;
                }
                ignoredPackages.add( ignore.getClassName().replace( '.', '/' ) );
            }

            final SignatureChecker signatureChecker =
                new SignatureChecker( new FileInputStream( signature ), ignoredPackages, new AntLogger( this ) );

            final List<File> tmp = new ArrayList<>();
            if (sourcepath != null) {
                for ( Object next : sourcepath )
                {
                    if ( next instanceof FileResource )
                    {
                        final File file = ( (FileResource) next ).getFile();
                        tmp.add( file );
                    }
                }
            }
            signatureChecker.setSourcePath(tmp);

            final Collection<String> annotationTypes = new HashSet<>();
            for ( Annotation annotation : annotations )
            {
                if ( annotation != null && annotation.getClassName() != null )
                {
                    annotationTypes.add( annotation.getClassName() );
                }
            }
            signatureChecker.setAnnotationTypes( annotationTypes );

            for ( Path path : paths )
            {
                final String[] files = path.list();
                for ( String file : files )
                {
                    signatureChecker.process( new File( file ) );
                }
            }

            if ( signatureChecker.isSignatureBroken() )
            {
                String message = "Signature errors found. Verify them and ignore them with the "
                                              + "proper annotation if needed.";
                if ( failOnError )
                {
                    throw new BuildException( message, getLocation() );
                }
                else
                {
                    log( message, Project.MSG_WARN );
                }
            }
        }
        catch ( IOException e )
        {
            throw new BuildException( "Failed to check signatures", e );
        }
    }

    protected void validate()
    {
        if ( signature == null )
        {
            throw new BuildException( "signature not set" );
        }
        if ( paths.size() < 1 )
        {
            throw new BuildException( "path not set" );
        }

    }

    /**
     * List of packages defined in the application.
     */
    private Set<String> buildPackageList()
        throws IOException
    {
        ClassListBuilder plb = new ClassListBuilder( new AntLogger( this ) );
        apply( plb );
        return plb.getPackages();
    }

    private void apply( ClassFileVisitor v )
        throws IOException
    {
        for ( Path path : paths )
        {
            final String[] files = path.list();
            for ( String file : files )
            {
                log( "Ignoring the signatures from file to be checked: " + file, Project.MSG_INFO );
                v.process( new File( file ) );
            }
        }
        if ( classpath != null )
        {
            for ( Object next : classpath )
            {
                if ( next instanceof FileResource )
                {
                    final File file = ( (FileResource) next ).getFile();
                    log( "Ignoring the signatures from classpath: " + file, Project.MSG_INFO );
                    v.process( file );
                }
            }
        }
    }
}
