package org.apache.maven.index;

/*
 * 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.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.lucene.queryParser.ParseException;
import org.apache.lucene.search.Query;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.maven.index.context.ContextMemberProvider;
import org.apache.maven.index.context.DefaultIndexingContext;
import org.apache.maven.index.context.ExistingLuceneIndexMismatchException;
import org.apache.maven.index.context.IndexCreator;
import org.apache.maven.index.context.IndexUtils;
import org.apache.maven.index.context.IndexingContext;
import org.apache.maven.index.context.MergedIndexingContext;
import org.apache.maven.index.context.StaticContextMemberProvider;
import org.apache.maven.index.context.UnsupportedExistingLuceneIndexException;
import org.apache.maven.index.expr.SearchExpression;
import org.apache.maven.index.util.IndexCreatorSorter;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.codehaus.plexus.logging.AbstractLogEnabled;
import org.codehaus.plexus.util.FileUtils;

/**
 * A default {@link NexusIndexer} implementation.
 *
 * @author Tamas Cservenak
 * @author Eugene Kuleshov
 * @deprecated Use {@link Indexer} instead. Discouraged from further use, as it suffers from multiple synchronization
 *             and other problems. As it tries to serve as "context registry" too, but it does not synchronize the
 *             access to it, but also, introduces some extras (like "targeted" vs "non targeted" search), that makes
 *             it's behavior less intuitive.
 */
@Deprecated
@Component( role = NexusIndexer.class )
public class DefaultNexusIndexer
    extends AbstractLogEnabled
    implements NexusIndexer
{

    @Requirement
    private Indexer indexer;

    @Requirement
    private Scanner scanner;

    @Requirement
    private IndexerEngine indexerEngine;

    @Requirement
    private QueryCreator queryCreator;

    private final Map<String, IndexingContext> indexingContexts = new ConcurrentHashMap<String, IndexingContext>();

    // ----------------------------------------------------------------------------
    // Contexts
    // ----------------------------------------------------------------------------

    public void addIndexingContext( IndexingContext context )
    {
        indexingContexts.put( context.getId(), context );
    }

    public IndexingContext addIndexingContext( String id, String repositoryId, File repository, File indexDirectory,
        String repositoryUrl, String indexUpdateUrl,
        List<? extends IndexCreator> indexers )
        throws IOException, UnsupportedExistingLuceneIndexException
    {
        try
        {
            IndexingContext context =
                indexer.createIndexingContext( id, repositoryId, repository, indexDirectory, repositoryUrl,
                                               indexUpdateUrl, true, false, indexers );
            indexingContexts.put( context.getId(), context );
            return context;
        }
        catch ( ExistingLuceneIndexMismatchException e )
        {
            throw new UnsupportedExistingLuceneIndexException( e.getMessage(), e );
        }
    }

    public IndexingContext addIndexingContextForced( String id, String repositoryId, File repository,
        File indexDirectory, String repositoryUrl, String indexUpdateUrl,
        List<? extends IndexCreator> indexers )
        throws IOException
    {
        IndexingContext context =
            indexer.createIndexingContext( id, repositoryId, repository, indexDirectory, repositoryUrl, indexUpdateUrl,
                                           true, true, indexers );
        indexingContexts.put( context.getId(), context );
        return context;
    }

    public IndexingContext addIndexingContext( String id, String repositoryId, File repository, Directory directory,
        String repositoryUrl, String indexUpdateUrl,
        List<? extends IndexCreator> indexers )
        throws IOException, UnsupportedExistingLuceneIndexException
    {
        try
        {
            IndexingContext context =
                new DefaultIndexingContext( id, repositoryId, repository, directory, repositoryUrl, indexUpdateUrl,
                                            IndexCreatorSorter.sort( indexers ), false );
            indexingContexts.put( context.getId(), context );
            return context;
        }
        catch ( ExistingLuceneIndexMismatchException e )
        {
            throw new UnsupportedExistingLuceneIndexException( e.getMessage(), e );
        }
    }

    public IndexingContext addIndexingContextForced( String id, String repositoryId, File repository,
        Directory directory, String repositoryUrl, String indexUpdateUrl,
        List<? extends IndexCreator> indexers )
        throws IOException
    {
        IndexingContext context =
            new DefaultIndexingContext( id, repositoryId, repository, directory, repositoryUrl, indexUpdateUrl,
                                        IndexCreatorSorter.sort( indexers ), true );
        indexingContexts.put( context.getId(), context );
        return context;
    }

    public IndexingContext addMergedIndexingContext( String id, String repositoryId, File repository,
        File indexDirectory, boolean searchable,
        Collection<IndexingContext> contexts )
        throws IOException
    {
        return addMergedIndexingContext( id, repositoryId, repository, indexDirectory, searchable,
                                         new StaticContextMemberProvider( contexts ) );
    }

    public IndexingContext addMergedIndexingContext( String id, String repositoryId, File repository,
        File indexDirectory, boolean searchable,
        ContextMemberProvider membersProvider )
        throws IOException
    {
        IndexingContext context =
            indexer.createMergedIndexingContext( id, repositoryId, repository, indexDirectory, searchable,
                                                 membersProvider );
        indexingContexts.put( context.getId(), context );
        return context;
    }

    public IndexingContext addMergedIndexingContext( String id, String repositoryId, File repository,
        Directory indexDirectory, boolean searchable,
        Collection<IndexingContext> contexts )
        throws IOException
    {
        IndexingContext context =
            new MergedIndexingContext( id, repositoryId, repository, indexDirectory, searchable,
                                       new StaticContextMemberProvider( contexts ) );
        indexingContexts.put( context.getId(), context );
        return context;
    }

    public IndexingContext addMergedIndexingContext( String id, String repositoryId, File repository,
        Directory indexDirectory, boolean searchable,
        ContextMemberProvider membersProvider )
        throws IOException
    {
        IndexingContext context =
            new MergedIndexingContext( id, repositoryId, repository, indexDirectory, searchable, membersProvider );
        indexingContexts.put( context.getId(), context );
        return context;
    }

    public void removeIndexingContext( IndexingContext context, boolean deleteFiles )
        throws IOException
    {
        if ( indexingContexts.containsKey( context.getId() ) )
        {
            indexingContexts.remove( context.getId() );
            indexer.closeIndexingContext( context, deleteFiles );
        }
    }

    public Map<String, IndexingContext> getIndexingContexts()
    {
        return Collections.unmodifiableMap( indexingContexts );
    }

    // ----------------------------------------------------------------------------
    // Scanning
    // ----------------------------------------------------------------------------

    public void scan( final IndexingContext context )
        throws IOException
    {
        scan( context, null );
    }

    public void scan( final IndexingContext context, boolean update )
        throws IOException
    {
        scan( context, null, update );
    }

    public void scan( final IndexingContext context, final ArtifactScanningListener listener )
        throws IOException
    {
        scan( context, listener, false );
    }

    public void scan( final IndexingContext context, final ArtifactScanningListener listener, final boolean update )
        throws IOException
    {
        scan( context, null, listener, update );
    }

    /**
     * Uses {@link Scanner} to scan repository content. A {@link ArtifactScanningListener} is used to process found
     * artifacts and to add them to the index using
     * {@link NexusIndexer#artifactDiscovered(ArtifactContext, IndexingContext)}.
     *
     * @see DefaultScannerListener
     * @see #artifactDiscovered(ArtifactContext, IndexingContext)
     */
    public void scan( final IndexingContext context, final String fromPath, final ArtifactScanningListener listener,
        final boolean update )
        throws IOException
    {
        final File repositoryDirectory = context.getRepository();
        if ( repositoryDirectory == null )
        {
            // nothing to scan
            return;
        }

        if ( !repositoryDirectory.exists() )
        {
            throw new IOException( "Repository directory " + repositoryDirectory + " does not exist" );
        }

        // always use temporary context when reindexing
        final File tmpFile = File.createTempFile( context.getId() + "-tmp", "" );
        final File tmpDir = new File( tmpFile.getParentFile(), tmpFile.getName() + ".dir" );
        if ( !tmpDir.mkdirs() )
        {
            throw new IOException( "Cannot create temporary directory: " + tmpDir );
        }

        IndexingContext tmpContext = null;
        try
        {
            final FSDirectory directory = FSDirectory.open( tmpDir );
            if ( update )
            {
                IndexUtils.copyDirectory( context.getIndexDirectory(), directory );
            }
            tmpContext = new DefaultIndexingContext( context.getId() + "-tmp", //
                                                     context.getRepositoryId(), //
                                                     context.getRepository(), //
                                                     directory, //
                                                     context.getRepositoryUrl(), //
                                                     context.getIndexUpdateUrl(), //
                                                     context.getIndexCreators(), //
                                                     true );

            scanner.scan( new ScanningRequest( tmpContext, //
                                               new DefaultScannerListener( tmpContext, indexerEngine,
                                                                           update, listener ), fromPath ) );

            tmpContext.updateTimestamp( true );
            context.replace( tmpContext.getIndexDirectory() );
        }
        catch ( Exception ex )
        {
            throw (IOException) new IOException( "Error scanning context " + context.getId() + ": " + ex ).initCause(
                ex );
        }
        finally
        {
            if ( tmpContext != null )
            {
                tmpContext.close( true );
            }

            if ( tmpFile.exists() )
            {
                tmpFile.delete();
            }

            FileUtils.deleteDirectory( tmpDir );
        }
    }

    /**
     * Delegates to the {@link IndexerEngine} to add a new artifact to the index
     */
    public void artifactDiscovered( ArtifactContext ac, IndexingContext context )
        throws IOException
    {
        if ( ac != null )
        {
            indexerEngine.index( context, ac );
        }
    }

    // ----------------------------------------------------------------------------
    // Modifying
    // ----------------------------------------------------------------------------

    /**
     * Delegates to the {@link IndexerEngine} to update artifact to the index
     */
    public void addArtifactToIndex( ArtifactContext ac, IndexingContext context )
        throws IOException
    {
        indexer.addArtifactsToIndex( Collections.singleton( ac ), context );
    }

    public void addArtifactsToIndex( Collection<ArtifactContext> acs, IndexingContext context )
        throws IOException
    {
        indexer.addArtifactsToIndex( acs, context );
    }

    /**
     * Delegates to the {@link IndexerEngine} to remove artifact from the index
     */
    public void deleteArtifactFromIndex( ArtifactContext ac, IndexingContext context )
        throws IOException
    {
        indexer.deleteArtifactsFromIndex( Collections.singleton( ac ), context );
    }

    public void deleteArtifactsFromIndex( Collection<ArtifactContext> acs, IndexingContext context )
        throws IOException
    {
        indexer.deleteArtifactsFromIndex( acs, context );
    }

    // ----------------------------------------------------------------------------
    // Searching
    // ----------------------------------------------------------------------------

    public FlatSearchResponse searchFlat( FlatSearchRequest request )
        throws IOException
    {
        if ( request.getContexts().isEmpty() )
        {
            request.getContexts().addAll( getIndexingContexts().values() );
        }
        return indexer.searchFlat( request );
    }

    public IteratorSearchResponse searchIterator( IteratorSearchRequest request )
        throws IOException
    {
        if ( request.getContexts().isEmpty() )
        {
            request.getContexts().addAll( getIndexingContexts().values() );
        }
        return indexer.searchIterator( request );
    }

    public GroupedSearchResponse searchGrouped( GroupedSearchRequest request )
        throws IOException
    {
        if ( request.getContexts().isEmpty() )
        {
            request.getContexts().addAll( getIndexingContexts().values() );
        }
        return indexer.searchGrouped( request );
    }

    // ----------------------------------------------------------------------------
    // Query construction
    // ----------------------------------------------------------------------------

    @Deprecated
    public Query constructQuery( Field field, String query, SearchType type )
        throws IllegalArgumentException
    {
        try
        {
            return queryCreator.constructQuery( field, query, type );
        }
        catch ( ParseException e )
        {
            throw new IllegalArgumentException( e );
        }
    }

    public Query constructQuery( Field field, SearchExpression expression )
        throws IllegalArgumentException
    {
        return indexer.constructQuery( field, expression );
    }

    // ----------------------------------------------------------------------------
    // Identification
    // ----------------------------------------------------------------------------

    public Collection<ArtifactInfo> identify( Field field, String query )
        throws IllegalArgumentException, IOException
    {
        return identify( constructQuery( field, query, SearchType.EXACT ) );
    }

    public Collection<ArtifactInfo> identify( File artifact )
        throws IOException
    {
        return identify( artifact, indexingContexts.values() );
    }

    public Collection<ArtifactInfo> identify( File artifact, Collection<IndexingContext> contexts )
        throws IOException
    {
        return indexer.identify( artifact, contexts );
    }

    public Collection<ArtifactInfo> identify( Query query )
        throws IOException
    {
        return identify( query, indexingContexts.values() );
    }

    public Collection<ArtifactInfo> identify( Query query, Collection<IndexingContext> contexts )
        throws IOException
    {
        return indexer.identify( query, contexts );
    }
}
