/*******************************************************************************
 * Copyright (c) 2007, 2008 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
 *******************************************************************************/ 
package org.eclipse.cdt.internal.core.pdom;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

import org.eclipse.cdt.core.CCorePlugin;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;

public class Checksums {
	private static final String KEY_ALGORITHM = "//algorithm//"; //$NON-NLS-1$
	private static final String DEFAULT_ALGORITHM = "MD5"; //$NON-NLS-1$

	/**
	 * Returns the default algorithm used to compute checksums.
	 * @throws NoSuchAlgorithmException 
	 * @since 4.0
	 */
	public static MessageDigest getDefaultAlgorithm() throws NoSuchAlgorithmException {
		return MessageDigest.getInstance(DEFAULT_ALGORITHM);
	}

	/**
	 * Retrieves the algorithm for computing checksums from the persisted map.
	 * @throws NoSuchAlgorithmException 
	 * @since 4.0
	 */
	public static MessageDigest getAlgorithm(Map<String, Object> persistedMap) throws NoSuchAlgorithmException {
		Object obj= persistedMap.get(KEY_ALGORITHM);
		String alg= obj instanceof String ? (String) obj : DEFAULT_ALGORITHM;
		return MessageDigest.getInstance(alg); 
	}
	
	/**
	 * Stores the algorithm in a map.
	 * @since 4.0
	 */
	public static void putAlgorithm(Map<String, Object> mapToPersist, MessageDigest md) {
		mapToPersist.put(KEY_ALGORITHM, md.getAlgorithm());
	}
	

	/**
	 * Computes the checksum for a given file.
	 */
	public static byte[] computeChecksum(MessageDigest md, File file) throws IOException {
        md.reset();
        FileInputStream fi= new FileInputStream(file);
        try {
            int read;
            byte[] buf= new byte[1024*64];
            while( (read= fi.read(buf)) >= 0) {
                md.update(buf, 0, read);
            }
            return md.digest();
        }
        finally {
            fi.close();
        }
	}

	/**
	 * Retrieves a checksum for a file from the persisted map. May return <code>null</code>.
	 * @since 4.0
	 */
	public static byte[] getChecksum(Map<String, Object> persistedMap, IFile file) {
		IPath prjRel= file.getProjectRelativePath();
		Object checksum= persistedMap.get(prjRel.toString());
		if (checksum instanceof byte[])
			return (byte[]) checksum;
		return null;
	}

	/**
	 * Stores a checksum in a map.
	 * @since 4.0
	 */
	public static void putChecksum(Map<String, Object> mapToPersist, IFile file, byte[] checksum) {
		IPath prjRel= file.getProjectRelativePath();
		mapToPersist.put(prjRel.toString(), checksum);
	}

	/**
	 * Creates a map to persist checksums for a project.
	 * @throws OperationCanceledException
	 * @since 4.0
	 */
	public static Map<String, Object> createChecksumMap(IFile[] tus, MessageDigest md, IProgressMonitor pm) 
			throws OperationCanceledException {
		Map<String, Object> result= new HashMap<String, Object>();
		putAlgorithm(result, md);
		pm.beginTask(Messages.Checksums_taskComputeChecksums, tus.length);
		for (IFile file : tus) {
			if (pm.isCanceled()) {
				throw new OperationCanceledException();
			}
			if (file != null) {
				IPath location= file.getLocation();
				if (location != null) {
					File f= location.toFile();
					if (f.exists()) {
						try {
							byte[] checksum= computeChecksum(md, f);
							putChecksum(result, file, checksum);
						}
						catch (IOException e) {
							CCorePlugin.log(e);
						}
					}
				}
			}
			pm.worked(1);
		}
		pm.done();
		return result;
	}
}
