package junkutil.crypt;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * MD5等のチェックサムを取得するクラス。
 * このクラスはJunkUtilの他のクラスに依存しません。
 * @author Hiroyuki Shiota
 */
public class MD5 {
	public static final String MD2 = "MD2";
	public static final String MD5 = "MD5";
	public static final String SHA = "SHA";
	public static final String SHA_1 = "SHA-1";
	public static final String SHA_256 = "SHA-256";
	public static final String SHA_384 = "SHA-384";
	public static final String SHA_512 = "SHA-512";
	
	/**
	 * デフォルトのアルゴリズム。
	 */
	private String algorithm = MD5;
	
	/**
	 * コンストラクタ。
	 */
	public MD5() {
	}
	
	/**
	 * アルゴリズムを指定するコンストラクタ。
	 * @param algorithm
	 */
	public MD5(String algorithm) {
		this.algorithm = algorithm;
	}
	
	/**
	 * 文字列のチェックサムを求める。
	 * @param str
	 * @return
	 * @throws NoSuchAlgorithmException
	 */
	public String crypt(String str) throws NoSuchAlgorithmException {
		if (str == null || str.length() == 0) {
			throw new IllegalArgumentException("str is null.");
		}
		return crypt(str.getBytes());
	}
	
	/**
	 * 指定のエンコーディングでの文字列のチェックサムを求める。
	 * @param str
	 * @param encoding
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws UnsupportedEncodingException
	 */
	public String crypt(String str, String encoding) throws NoSuchAlgorithmException, UnsupportedEncodingException {
		if (str == null || str.length() == 0) {
			throw new IllegalArgumentException("str is null.");
		}
		if (encoding == null) {
			throw new IllegalArgumentException("encoding is null.");
		}
		return crypt(str.getBytes(encoding));
	}
	
	/**
	 * byte[]のチェックサムを求める。
	 * @param byteArray
	 * @return
	 * @throws NoSuchAlgorithmException
	 */
	public String crypt(byte [] byteArray) throws NoSuchAlgorithmException {
		if (byteArray == null || byteArray.length == 0) {
			throw new IllegalArgumentException("bytes is null");
		}
		MessageDigest digest = MessageDigest.getInstance(algorithm);
		byte[] hash = digest.digest(byteArray);
		return asHex(hash);
	}
	
	/**
	 * ファイルなどのチェックサムを求める。
	 * @param in
	 * @param buffSize
	 * @return
	 * @throws NoSuchAlgorithmException
	 * @throws IOException
	 */
	public String crypt(InputStream in, int buffSize) throws NoSuchAlgorithmException, IOException {
		if (in == null || in.available() == 0) {
			throw new IllegalArgumentException("InputStream is null.");
		}
		MessageDigest digest = MessageDigest.getInstance(algorithm);
		try {
			byte[] buff = new byte[buffSize];
			int len = 0;
			while ((len = in.read(buff, 0, buff.length)) >= 0) {
				digest.update(buff, 0, len);
			}
		} catch (IOException e) {
			throw e;
		} finally {
			if (in != null) {
				try {
					in.close();
				} catch (IOException e) {
				}
			}
		}
		byte[] hash = digest.digest();
		return asHex(hash);
	}
	
	/**
	 * バイトを16進数に変換する。
	 * @param byteArray
	 * @return
	 */
	public static String asHex(byte[] byteArray) {
		StringBuffer hexString = new StringBuffer();
		for (int i = 0; i < byteArray.length; i++) {
			if ((0xff & byteArray[i]) < 0x10) {
				hexString.append("0" + Integer.toHexString((0xFF & byteArray[i])));
			} else {
				hexString.append(Integer.toHexString(0xFF & byteArray[i]));
			}
		}
		
		return new String(hexString);
	}
	
	/**
	 * 16進数をバイトに変換する
	 * @param hex
	 * @return
	 */
	public static byte[] asByteArray(String hex) {
		byte[] bytes = new byte[hex.length() / 2];
		for (int i = 0; i < bytes.length; i++) {
			bytes[i] = (byte)Integer.parseInt(hex.substring(i * 2, (i + 1) * 2), 16);
		}
		return bytes;
	}
}
