/**
 * 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];

    /**
     * Default constructor
     */
    private Utils() { }

    /**
     * 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 if IO error
     */
    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 if IO error
     */
    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  input file
     * @param outFile output file
     * @throws IOException if IO error
     */
    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 if IO error
     */
    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();
    }
}
