package de.intarsys.tools.authenticate;

import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.KeySpec;
import java.util.Random;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;

import de.intarsys.tools.encoding.Base64;
import de.intarsys.tools.string.StringTools;

/**
 * A tool class for handling passwords and authentication
 * 
 */
public class PasswordTools {

	final private static String CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDREFGHIJKLMNOPQRSTUVWXYZ";

	private static Cipher ecipher;

	private static Cipher dcipher;

	/**
	 * Create a random password of length <code>length</code>.
	 * 
	 * @param length
	 * @return A new random password.
	 */
	static public char[] createRandom(int length) {
		Random rand = new Random(System.currentTimeMillis());
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i <= length; i++) {
			int pos = rand.nextInt(CHARS.length());
			sb.append(CHARS.charAt(pos));
		}
		return sb.toString().toCharArray();
	}

	/**
	 * Decrypt a byte array which was previously encrypted using
	 * <code>encrypt</code>. Provided the same salt and passphrase are used for
	 * initialization, this method returns the original unencrypted input.
	 * 
	 * @param bytes
	 * @return The decrypted representation of <code>bytes</code>
	 */
	static public byte[] decrypt(byte[] bytes) {
		try {
			return dcipher.doFinal(bytes);
		} catch (BadPaddingException e) {
		} catch (IllegalBlockSizeException e) {
		}
		return null;
	}

	/**
	 * Decrypt a string which was previously encrypted using
	 * <code>encrypt</code>. Provided the same salt and passphrase are used for
	 * initialization, this method returns the original unencrypted input.
	 * 
	 * @param value
	 * @return The decrypted representation of <code>value</code>
	 */
	static public char[] decrypt(String value) {
		try {
			byte[] bytes = Base64.decode(StringTools.toByteArray(value));
			byte[] decrypted = decrypt(bytes);
			return new String(decrypted, "UTF8").toCharArray();
		} catch (UnsupportedEncodingException e) {
			// must have encoding
			return null;
		}
	}

	/**
	 * Encrypt a clear text array of bytes. The result is the plain encrypted
	 * byte array.
	 * 
	 * @param bytes
	 * @return The encrpyted representation of <code>bytes</code>
	 */
	static public byte[] encrypt(byte[] bytes) {
		try {
			return ecipher.doFinal(bytes);
		} catch (BadPaddingException e) {
		} catch (IllegalBlockSizeException e) {
		}
		return null;
	}

	/**
	 * Encrypt a clear text array of chars. The result is a Base64 encoded
	 * string version of the encrypted UTF-8 encoded input bytes.
	 * 
	 * @param value
	 * @return An encrypted, invertible representation of <code>value</code>
	 */
	static public String encrypt(char[] value) {
		try {
			byte[] bytes = new String(value).getBytes("UTF8");
			byte[] encrypted = encrypt(bytes);
			return new String(Base64.encode(encrypted));
		} catch (UnsupportedEncodingException e) {
			// must have encoding
			return null;
		}
	}

	/**
	 * A one way hash for a clear text password.
	 * 
	 * @param password
	 * @return A one way hash for a clear text password.
	 */
	static public String hash(char[] password) {
		if (StringTools.isEmpty(password)) {
			return "";
		}
		MessageDigest md = null;
		try {
			md = MessageDigest.getInstance("SHA-1");
		} catch (NoSuchAlgorithmException e) {
			return new String(password);
		}
		try {
			md.update(new String(password).getBytes("UTF-8"));
		} catch (UnsupportedEncodingException e) {
			// can't happen
			return new String(password);
		}
		byte raw[] = md.digest();
		return new String(Base64.encode(raw));
	}

	/**
	 * Initialize the {@link PasswordTools}.
	 * 
	 * @param salt
	 * @param passphrase
	 */
	static public void initialize(byte[] salt, char[] passphrase) {
		try {
			int iterationCount = 19;
			// 
			KeySpec keySpec = new PBEKeySpec(passphrase, salt, iterationCount);
			SecretKey key = SecretKeyFactory.getInstance("PBEWithMD5AndDES")
					.generateSecret(keySpec);

			ecipher = Cipher.getInstance(key.getAlgorithm());
			dcipher = Cipher.getInstance(key.getAlgorithm());

			AlgorithmParameterSpec paramSpec = new PBEParameterSpec(salt,
					iterationCount);

			ecipher.init(Cipher.ENCRYPT_MODE, key, paramSpec);
			dcipher.init(Cipher.DECRYPT_MODE, key, paramSpec);
		} catch (Exception e) {
			throw new IllegalStateException("can't initialize password tools",
					e);
		}
	}
}
