/*******************************************************************************
 * Copyright (c) 2007 Wind River Systems, Inc. and others.
 * 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:
 *    Markus Schorn - initial API and implementation
 * 	  IBM Corporation
 *******************************************************************************/ 

package org.eclipse.cdt.internal.core.indexer;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.cdt.core.parser.IParserLogService;
import org.eclipse.cdt.core.parser.IScannerInfo;
import org.eclipse.cdt.internal.core.index.IWritableIndex;
import org.eclipse.cdt.internal.core.pdom.IndexerProgress;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;

/**
 * The base class for standalone index population tools.
 * 
 * <p>
 * <strong>EXPERIMENTAL</strong>. This class or interface has been added as
 * part of a work in progress. There is no guarantee that this API will work or
 * that it will remain the same. Please do not use this API without consulting
 * with the CDT team.
 * 
 * This class is not thread safe.
 * </p>
 * 
 * @since 4.0
 */
public abstract class StandaloneIndexer {
	
	/**
	 * Parser should not skip any references.
	 */
	public static final int SKIP_NO_REFERENCES= 0;
	
	/**
	 * Parser to skip all references.
	 */
	public static final int SKIP_ALL_REFERENCES= 1; 
	
	/**
	 * Parser to skp type references.
	 */
	public static final int SKIP_TYPE_REFERENCES= 2;
	
	/**
	 * Constant for indicating to update all translation units.
	 */
	public final static int UPDATE_ALL= 0x1;

	/**
	 * Constant for indicating to update translation units if their timestamp
	 * has changed.
	 */
	public final static int UPDATE_CHECK_TIMESTAMPS= 0x2;
		
	/**
	 * Empty list.
	 */
	protected static final List<String> NO_TUS = Collections.emptyList();
	
	
	/**
	 * The IWritableIndex that stores all bindings and names.
	 */
	protected IWritableIndex fIndex;
	
	/**
	 * A flag that indiciates if all files (sources without config, headers not included)
	 * should be parsed.
	 */
	protected boolean fIndexAllFiles;
	
	/**
	 * Collection of valid file extensions for C/C++ source.
	 */
	protected Set<String> fValidSourceUnitNames;
	
	/**
	 * The IScannerInfo that provides include paths and defined symbols.
	 * Either a single scanner info or a IStandaloneScannerInfoProvider must
	 * be provided, but not both. If a single IScannerInfo object is provided
	 * it will always be used. Otherwise the provider will be used.
	 */
	protected IScannerInfo fScanner;
	
	/**
	 * Creates IScannerInfo objects from file paths, allows there
	 * to be separate scanner infos for specific files and folders.
	 */
	protected IStandaloneScannerInfoProvider fScannerInfoProvider;
	
	/**
	 * The ILanguageMapper that determines the ILanguage for a file.
	 */
	protected ILanguageMapper fMapper;
	
	/**
	 * The logger during parsing.
	 */
	protected IParserLogService fLog;
	
	/**
	 * A flag that indicates if all activities during indexing should be shown.
	 */
	protected boolean fShowActivity;
	
	/**
	 * A flag that indicates if any problems encountered during indexing.
	 * should be shown.
	 */
	protected boolean fShowProblems;
	
	/**
	 * A flag that indicates if statistics should be gathered during indexing.
	 */
	protected boolean fTraceStatistics;
	
	/**
	 * The type of references the parser should skip.
	 */
	protected int fSkipReferences = SKIP_NO_REFERENCES;
	
	/**
	 * The exclusion filter that skips over files that should not be indexed.
	 */
	protected FilenameFilter fExclusionFilter;
	
	/**
	 * Files to parse up front.
	 */
	protected String[] fFilesToParseUpFront = new String[0];
	
	protected int fUpdateOptions = UPDATE_ALL;
	
	private IndexerProgress fProgress = null;
	private volatile StandaloneIndexerTask fDelegate;
	
	private static FilenameFilter DEFAULT_FILTER = new FilenameFilter() {
		public boolean accept(File dir, String name) {
			return true;
		}
	};
	
	public StandaloneIndexer(IWritableIndex index, boolean indexAllFiles,  
			                 ILanguageMapper mapper, IParserLogService log, IScannerInfo scanner) {
		fIndex = index;
		fIndexAllFiles = indexAllFiles;
		fMapper = mapper;
		fLog = log;	
		fScanner = scanner;
		fScannerInfoProvider = null;
	}
	
	
	public StandaloneIndexer(IWritableIndex index, boolean indexAllFiles,  
            ILanguageMapper mapper, IParserLogService log, IStandaloneScannerInfoProvider scannerProvider) {
		fIndex = index;
		fIndexAllFiles = indexAllFiles;
		fMapper = mapper;
		fLog = log;	
		fScanner = null;
		fScannerInfoProvider = scannerProvider;
	}
	

	public void setScannerInfoProvider(IStandaloneScannerInfoProvider provider) {
		fScannerInfoProvider = provider;
		fScanner = null;
	}
	
	/**
	 * Returns the index.
	 * @return the IWritable index the indexer is writing to
	 */
	public IWritableIndex getIndex() {
		return fIndex;
	}
	
	/**
	 * Returns true if all files (sources without config, headers not included)
	 * should be parsed.  Otherwise, this method returns false.
	 */
	public boolean getIndexAllFiles() {
		return fIndexAllFiles;
	}
	
	/**
	 * Returns the collection of valid file extensions for C/C++ source.
	 */
	public Set<String> getValidSourceUnitNames() {
		return fValidSourceUnitNames;
	}
	
	/**
	 * Sets the collection of valid file extensions for C/C++ source.
	 */
	public void setValidSourceUnitNames(Set<String> validSourceUnitNames) {
		fValidSourceUnitNames = validSourceUnitNames;
	}
	
	/**
	 * Returns the IScannerInfo that provides include paths and defined symbols.
	 */
	public IScannerInfo getScannerInfo() {
		return fScanner;
	}
	
	
	/**
	 * Returns the IScannerInfo for the given path.
	 * If the current instance was created with an IScannerInfo instead of
	 * an IScannerInfoProvider then the path will be ignored and
	 * that IScannerInfo will always be returned.
	 */
	public IScannerInfo getScannerInfo(String path) {
		if(fScanner != null)
			return fScanner;
		
		return fScannerInfoProvider.getScannerInformation(path);
	}
	
	
	/**
	 * Returns the ILanguageMapper that determines the ILanguage for a file.
	 */
	public ILanguageMapper getLanguageMapper() {
		return fMapper;
	}
	
	/**
	 * Returns the logger.
	 */
	public IParserLogService getParserLog() {
		return fLog;
	}
	
	/**
	 * Returns true if indexing activities should be shown.
	 * Otherwise, this method returns false.
	 */
	public boolean getShowActivity() {
		return fShowActivity;
	}
	
	/**
	 * Tells indexer if indexing activities should be shown.
	 */
	public void setShowActivity(boolean showActivity) {
		fShowActivity = showActivity;
	}
	
	/**
	 * Returns true if problems during indexing should be shown.
	 * Otherwise, this method returns false.
	 */
	public boolean getShowProblems() {
		return fShowProblems;
	}
	
	/**
	 * Tells indexer if problems during indexing should be shown.
	 */
	public void setShowProblems(boolean showProblems) {
		fShowProblems = showProblems;
	}
	
	/**
	 * Returns true if statistics should be gathered during indexing.
	 * Otherwise, this method returns false..
	 */
	public boolean getTraceStatistics() {
		return fTraceStatistics;
	}
	
	/**
	 * Tells indexer if statistics should be gathered during indexing.
	 */
	public void setTraceStatistics(boolean traceStatistics) {
		fTraceStatistics = traceStatistics;
	}
	
	private IndexerProgress createProgress() {
		IndexerProgress progress= new IndexerProgress();
		progress.fTimeEstimate= 1000;
		return progress;
	}
	
	private void clearIndex() throws CoreException, InterruptedException {
		IWritableIndex index= getIndex();
		// First clear the pdom
		index.acquireWriteLock(0);
		try {
			index.clear();
		}
		finally {
			index.releaseWriteLock(0);
		}
	}
	
	/**
	 * Returns the progress information.
	 */
	public synchronized IndexerProgress getProgressInformation() {
		return fDelegate != null ? fDelegate.getProgressInformation() : fProgress;
	}
	
	/**
	 * Returns the update options specified.
	 */
	public int getUpdateOptions() {
		return fUpdateOptions;
	}
	
	/**
	 * Specifies the update options, whether all translation units should be updated or only the ones
	 * with timestamp changes.
	 * @param options
	 */
	public void setUpdateOptions (int options) {
		fUpdateOptions = options;
	}
		
	/**
	 * Clears the index and rebuild
	 * @param tus - directories/files to be added to index
	 * @param monitor
	 * @throws IOException
	 */
	public void rebuild(List<String> tus, IProgressMonitor monitor) throws IOException {
		fProgress = createProgress();
		
		try {
			clearIndex();
			fDelegate= createTask(getFilesAdded(tus), NO_TUS, NO_TUS);
			fDelegate.setUpdateFlags(fUpdateOptions);
			
			if (fDelegate != null) {
				fDelegate.run(monitor);
			}
		} catch (CoreException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
		}
	}
	
	/**
	 * Updates the index with changes.
	 * @param added - directories/files to be added to the index
	 * @param changed - files that have been changed
	 * @param removed - files to be removed from the index
	 * @param monitor
	 * @throws IOException
	 */
	public void handleDelta(List<String> added, List<String> changed, List<String> removed, IProgressMonitor monitor) throws IOException {
		fProgress= new IndexerProgress();
				
		fDelegate= createTask(getFilesAdded(added), changed, removed);
		if (fDelegate != null) {
			try {
				fDelegate.setUpdateFlags(fUpdateOptions);
				fDelegate.run(monitor);
			} catch (InterruptedException e) {
			}
		}
		
	}
	
	/**
	 * Returns files that are being added to the index, skipping over files that 
	 * should not be excluded.
	 * @param tus
	 * @return
	 */
	private List<String> getFilesAdded(List<String> tus) {
		List<String> added = new ArrayList<String>();
		
		FilenameFilter filter = getExclusionFilter();
		if (filter == null) {
			filter = DEFAULT_FILTER;
		}
		
		Iterator<String> iter = tus.iterator();
		while (iter.hasNext()) {
			String path = iter.next();
			File file = new File(path);
			if (file.isDirectory()) {
				String[] files = file.list(filter);
				for (String file2 : files) {
					added.add(file2);
				}
			}
			else {				
				if (filter.accept(file.getParentFile(), file.getName())) {
					added.add(path);
				}
			}
		}
		return added;
	}
	
	/**
	 * Creates a delegate standalone indexing task
	 */
	protected abstract StandaloneIndexerTask createTask(List<String> added, List<String> changed, List<String> removed);

	/**
	 * Return the type of references the parser should skip.
	 */
	public int getSkipReferences() {
		return fSkipReferences;
	}
	
	/**
	 * Sets the type of references the parser should skip.
	 * @param skipReferences
	 */
	public void setSkipReferences(int skipReferences) {
		fSkipReferences = skipReferences;
	}

	/**
	 * Returns an array of files that should be parsed up front.
	 */
	public String[] getFilesToParseUpFront() {
		return fFilesToParseUpFront;
	}
	
	/**
	 * Sets an array of files that should be parsed up front.
	 * @param filesToParseUpFront
	 */
	public void setFilesToParseUpFront(String[] filesToParseUpFront) {
		fFilesToParseUpFront = filesToParseUpFront;
	}
	
	/**
	 * Returns the exclusion filter for this indexer.
	 */
	public FilenameFilter getExclusionFilter() {
		return fExclusionFilter;
	}
	
	/**
	 * Sets the exclusion filter that tells the indexer to skip over 
	 * files that should not be indexed.
	 * @param exclusionFilter
	 */
	public void setExclusionFilter(FilenameFilter exclusionFilter) {
		fExclusionFilter = exclusionFilter;
	}
}
