/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.curate;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Utils contains a few commonly occurring methods.
 *
 * @author richardrodgers
 */
public class Utils
{
    private static final int BUFF_SIZE = 4096;
    // we can live with 4k preallocation
    private static final byte[] buffer = new byte[BUFF_SIZE];
    
    /**
     * Calculates and returns a checksum for the passed file using the passed
     * algorithm.
     * 
     * @param file
     *        file on which to calculate checksum
     * @param algorithm
     *        string for algorithm: 'MD5', 'SHA1', etc
     * @return checksum
     *        string of the calculated checksum
     *        
     * @throws IOException
     */
    public static String checksum(File file, String algorithm) throws IOException
    {
        InputStream in = null;
        String chkSum = null;
        try
        {
            in = new FileInputStream(file);
            chkSum = checksum(in, algorithm);
        }
        finally
        {
            if (in != null)
            {
               in.close(); 
            }
        }
        return chkSum;
    }

    /**
     * Calculates and returns a checksum for the passed IO stream using the passed
     * algorithm.
     * 
     * @param in
     *        input stream on which to calculate checksum
     * @param algorithm
     *        string for algorithm: 'MD5', 'SHA1', etc
     * @return checksum
     *        string of the calculated checksum
     *        
     * @throws IOException
     */
    public static String checksum(InputStream in, String algorithm) throws IOException
    {
        try
        {
            DigestInputStream din = new DigestInputStream(in,
                                        MessageDigest.getInstance(algorithm));
            while (true)
            {
                synchronized (buffer)
                {
                    if (din.read(buffer) == -1)
                    {
                        break;
                    }
                    // otherwise, a no-op
                }
            }
            return toHex(din.getMessageDigest().digest());
        } catch (NoSuchAlgorithmException nsaE) {
            throw new IOException(nsaE.getMessage(), nsaE);
        }
    }

    /**
     * Reasonably efficient Hex checksum converter
     * 
     * @param data
     *        byte array
     * @return hexString
     *        checksum
     */
    static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();
    public static String toHex(byte[] data) {
         if ((data == null) || (data.length == 0)) {
            return null;
        }
        char[] chars = new char[2 * data.length];
        for (int i = 0; i < data.length; ++i) {
            chars[2 * i] = HEX_CHARS[(data[i] & 0xF0) >>> 4];
            chars[2 * i + 1] = HEX_CHARS[data[i] & 0x0F];
        }
        return new String(chars);
    }
    
    /**
     * Performs a buffered copy from one file into another.
     * 
     * @param inFile
     * @param outFile
     * @throws IOException
     */
    public static void copy(File inFile, File outFile) throws IOException
    {
        FileInputStream in = null;
        FileOutputStream out = null;
        try
        {
            in = new FileInputStream(inFile);
            out = new FileOutputStream(outFile);
            copy(in, out);
        }
        finally
        {
            if (in != null)
            {
                in.close();
            }
            
            if (out != null)
            {
                out.close();
            }
        }
    }

    /**
     * Performs a buffered copy from one IO stream into another. Note that stream
     * closure is responsibility of caller.
     * 
     * @param in
     *        input stream
     * @param out
     *        output stream
     * @throws IOException
     */
    public static void copy(InputStream in, OutputStream out) throws IOException
    {
        while (true)
        {
            synchronized (buffer)
            {
                int count = in.read(buffer);
                if (-1 == count)
                {
                    break;
                }
                // write out those same bytes
                out.write(buffer, 0, count);
            }
        }
        // needed to flush cache
        out.flush();
    }
}
